Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .chloggen/50914-postgresqlreceiver-table-size-total.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
change_type: bug_fix

component: receiver/postgresql

note: Fix `postgresql.table.size` to report total disk space used by a table, including its indexes and TOAST data

issues: [50914]

subtext:

change_logs: [user]
2 changes: 1 addition & 1 deletion receiver/postgresqlreceiver/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ func (c *postgreSQLClient) getDatabaseTableMetrics(ctx context.Context, db strin
s.n_tup_del AS del,
s.n_tup_hot_upd AS hot_upd,
s.seq_scan AS seq_scans,
pg_relation_size(s.relid) AS table_size,
pg_total_relation_size(s.relid) AS table_size,
s.vacuum_count
FROM pg_stat_user_tables s
LEFT JOIN (
Expand Down
2 changes: 1 addition & 1 deletion receiver/postgresqlreceiver/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ Number of user tables in a database.

### postgresql.table.size

Disk space used by a table.
Total disk space used by a table, including its indexes and TOAST data.

| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- |
Expand Down
126 changes: 126 additions & 0 deletions receiver/postgresqlreceiver/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1021,3 +1021,129 @@ func tableCountEquivalenceTest(pgVersion string) func(*testing.T) {
assert.Equal(t, int64(len(tableMetrics)), count, "cheap table count must equal the full per-table query's row count")
}
}

// TestTableSizeIncludesIndexesAndToast is a regression test for the table_size
// query using pg_relation_size, which silently excludes a table's indexes and
// TOAST storage. Both fixture tables are built so pg_total_relation_size
// exceeds pg_relation_size by an unambiguous, multi-page margin: reverting to
// pg_relation_size (or only adding index size, but forgetting TOAST) fails
// this test rather than passing by a rounding coincidence.
//
// Run against both sides of the PG14 pg_stat_user_tables boundary that
// tableCountEquivalenceTest also straddles: pg_total_relation_size and TOAST
// storage predate both test versions, but nothing else in this file exercises
// table_size against a real database on pre17TestVersion.
func TestTableSizeIncludesIndexesAndToast(t *testing.T) {
t.Run("pre17", tableSizeIncludesIndexesAndToastTest(pre17TestVersion))
t.Run("post17", tableSizeIncludesIndexesAndToastTest(post17TestVersion))
}

func tableSizeIncludesIndexesAndToastTest(pgVersion string) func(*testing.T) {
return func(t *testing.T) {
ci, err := testcontainers.GenericContainer(
t.Context(),
testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Image: fmt.Sprintf("postgres:%s", pgVersion),
Env: map[string]string{
"POSTGRES_USER": "root",
"POSTGRES_PASSWORD": "otel",
"POSTGRES_DB": "otel",
},
Files: []testcontainers.ContainerFile{{
HostFilePath: filepath.Join("testdata", "integration", "03-table-size-init.sql"),
ContainerFilePath: "/docker-entrypoint-initdb.d/01-init.sql",
FileMode: 700,
}},
ExposedPorts: []string{postgresqlPort},
// A listening port is not enough: the postgres image runs a temporary
// server for its init scripts, so the port accepts connections while the
// real server is still "starting up" (57P03). The readiness log line is
// emitted twice -- once for the init server, once for the real one -- so
// waiting for the second occurrence guarantees the DB is ready to query.
WaitingFor: wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).
WithStartupTimeout(2 * time.Minute),
},
},
)
require.NoError(t, err)
defer testcontainers.CleanupContainer(t, ci)

require.NoError(t, ci.Start(t.Context()))

p, err := ci.MappedPort(t.Context(), postgresqlPort)
require.NoError(t, err)

clientDB, err := getDB(t.Context(), postgreSQLConfig{
username: "otelu",
password: "otelp",
address: confignet.AddrConfig{
Endpoint: net.JoinHostPort("localhost", p.Port()),
},
tls: configtls.ClientConfig{
Insecure: true,
},
}, "otel")
require.NoError(t, err)

client := postgreSQLClient{client: clientDB, closeFn: clientDB.Close}
defer func() {
require.NoError(t, client.Close())
}()

// The receiver's own connection runs as otelu, so compute the reference
// values it can actually see rather than as the superuser -- a permission
// gap here would otherwise surface as a silent 0, not a test failure.
referenceQuery := `SELECT
c.relname,
pg_total_relation_size(c.oid),
pg_relation_size(c.oid),
pg_indexes_size(c.oid),
COALESCE(pg_total_relation_size(c.reltoastrelid), 0)
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname IN ('big_index_table', 'toasted_table') AND n.nspname = 'public';`

ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second)
defer cancel()

rows, err := clientDB.QueryContext(ctx, referenceQuery)
require.NoError(t, err)
defer rows.Close()

type reference struct {
totalSize, relationSize, indexesSize, toastSize int64
}
referenceByTable := map[string]reference{}
for rows.Next() {
var name string
var ref reference
require.NoError(t, rows.Scan(&name, &ref.totalSize, &ref.relationSize, &ref.indexesSize, &ref.toastSize))
referenceByTable[name] = ref
}
require.NoError(t, rows.Err())
require.Len(t, referenceByTable, 2, "reference query should see both fixture tables")

tableMetrics, err := client.getDatabaseTableMetrics(ctx, "otel")
require.NoError(t, err)

indexRef := referenceByTable["big_index_table"]
require.Positive(t, indexRef.indexesSize, "fixture bug: index has no measurable size, assertion below would be vacuous")
indexStats, ok := tableMetrics[tableKey("otel", "public", "big_index_table")]
require.True(t, ok, "big_index_table missing from getDatabaseTableMetrics result")
assert.Equal(t, indexRef.totalSize, indexStats.size,
"postgresql.table.size must equal pg_total_relation_size, not pg_relation_size alone")
assert.Greater(t, indexStats.size, indexRef.relationSize,
"reported size must exceed the data-only size now that the table has a non-trivial index")

toastRef := referenceByTable["toasted_table"]
require.Positive(t, toastRef.toastSize, "fixture bug: no TOAST data was created, assertion below would be vacuous")
toastStats, ok := tableMetrics[tableKey("otel", "public", "toasted_table")]
require.True(t, ok, "toasted_table missing from getDatabaseTableMetrics result")
assert.Equal(t, toastRef.totalSize, toastStats.size,
"postgresql.table.size must equal pg_total_relation_size, not pg_relation_size alone")
assert.Greater(t, toastStats.size, toastRef.relationSize+toastRef.indexesSize,
"reported size must include TOAST data, not just the main heap and indexes")
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion receiver/postgresqlreceiver/metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ metrics:
unit: "{table}"
attributes: [db.namespace]
postgresql.table.size:
description: Disk space used by a table.
description: Total disk space used by a table, including its indexes and TOAST data.
stability: development
enabled: true
unit: By
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
CREATE USER otelu WITH PASSWORD 'otelp';
GRANT SELECT ON pg_stat_database TO otelu;
GRANT pg_monitor TO otelu;

-- big_index_table: enough rows for its primary-key btree index to be a
-- meaningful, multi-page size on its own (a handful of rows would round to
-- the same single page as an empty index, making the assertion flaky).
-- Measured on postgres:17.2: 5000 rows -> index size roughly matches the
-- data size, so pg_total_relation_size is comfortably ~2x pg_relation_size.
CREATE TABLE big_index_table (
id serial PRIMARY KEY,
val integer NOT NULL
);
INSERT INTO big_index_table (val)
SELECT g FROM generate_series(1, 5000) AS g;

-- toasted_table: a wide text column pushed out-of-line into TOAST storage.
-- Postgres only TOASTs values that make a row exceed roughly a quarter of
-- the page size, so each value here is well past that threshold. Measured
-- on postgres:17.2: 50 such rows already push the TOAST relation to several
-- times the size of the main heap and its index combined.
CREATE TABLE toasted_table (
id serial PRIMARY KEY,
payload text NOT NULL
);
INSERT INTO toasted_table (payload)
SELECT repeat('x', 20000) FROM generate_series(1, 50);

VACUUM ANALYZE big_index_table;
VACUUM ANALYZE toasted_table;

GRANT SELECT ON big_index_table, toasted_table TO otelu;
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -1124,7 +1124,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -1284,7 +1284,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -1444,7 +1444,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ resourceMetrics:
timeUnixNano: "1706802526712082422"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -524,7 +524,7 @@ resourceMetrics:
timeUnixNano: "1706802526712082422"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -979,7 +979,7 @@ resourceMetrics:
timeUnixNano: "1706802526712082422"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -1139,7 +1139,7 @@ resourceMetrics:
timeUnixNano: "1706802526712082422"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -1130,7 +1130,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -1293,7 +1293,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -1456,7 +1456,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -897,7 +897,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -1057,7 +1057,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down Expand Up @@ -1217,7 +1217,7 @@ resourceMetrics:
timeUnixNano: "2000000"
isMonotonic: true
unit: '{sequential_scan}'
- description: Disk space used by a table.
- description: Total disk space used by a table, including its indexes and TOAST data.
name: postgresql.table.size
sum:
aggregationTemporality: 2
Expand Down
Loading
Loading