Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c2b6584
instrumentation: Use Instrumentation instead of manual buffer tracking
lfittl Apr 5, 2026
0a44085
instrumentation: Replace direct changes of pgBufferUsage/pgWalUsage w…
lfittl Mar 27, 2026
1589222
instrumentation: Add additional regression tests covering buffer usage
lfittl Apr 5, 2026
e5a46c1
Optimize measuring WAL/buffer usage through stack-based instrumentation
lfittl Apr 6, 2026
99fb6c8
instrumentation: Retire diff-based buffer usage tracking and pgBuffer…
lfittl Oct 3, 2026
203fb3b
instrumentation: Use Instrumentation struct for parallel workers
lfittl Mar 16, 2026
b7d48b2
instrumentation: Optimize ExecProcNodeInstr instructions by inlining
lfittl Apr 6, 2026
c52afd4
pgstat: Derive pg_stat_database I/O timings from instrumentation
lfittl Oct 3, 2026
f8f9ed0
Index scans: Show table buffer accesses separately in EXPLAIN ANALYZE
lfittl Oct 3, 2026
8ef30a4
Add test_session_buffer_usage test module
lfittl Mar 7, 2026
6843285
Fix EPQ
lfittl Oct 4, 2026
99ccbaf
Fix up parallel index scan heap access tracking
lfittl Oct 4, 2026
84150de
fix up tests
lfittl Oct 4, 2026
7cb26a4
fix up main stack commit
lfittl Oct 4, 2026
4aaa95c
Test cursor handling in test module
lfittl Oct 4, 2026
4cf23e9
WIP - change how memory contexts are handled.
lfittl Oct 4, 2026
02f76b4
WIP - fix up cursor test
lfittl Oct 4, 2026
1396c8f
fix trigger instrumentation allocation
lfittl Oct 4, 2026
b7b36fc
fix main stack commit for trigger instrumentation
lfittl Oct 4, 2026
6fd48ea
fix up test
lfittl Oct 4, 2026
50aea9e
fix main stack
lfittl Oct 4, 2026
190062d
fix table buffers
lfittl Oct 4, 2026
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
70 changes: 70 additions & 0 deletions contrib/pg_stat_statements/expected/utility.out
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,76 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
(3 rows)

-- Buffer stats should flow through EXPLAIN ANALYZE
CREATE TEMP TABLE flow_through_test (a int, b char(200));
INSERT INTO flow_through_test SELECT i, repeat('x', 200) FROM generate_series(1, 5000) AS i;
CREATE FUNCTION run_explain_buffers_test() RETURNS void AS $$
DECLARE
BEGIN
EXECUTE 'EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM flow_through_test';
END;
$$ LANGUAGE plpgsql;
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
---
t
(1 row)

SELECT run_explain_buffers_test();
run_explain_buffers_test
--------------------------

(1 row)

-- EXPLAIN entries should have non-zero buffer stats
SELECT query, local_blks_hit + local_blks_read > 0 as has_buffer_stats
FROM pg_stat_statements
WHERE query LIKE 'SELECT run_explain_buffers_test%'
ORDER BY query COLLATE "C";
query | has_buffer_stats
-----------------------------------+------------------
SELECT run_explain_buffers_test() | t
(1 row)

DROP FUNCTION run_explain_buffers_test;
DROP TABLE flow_through_test;
-- Validate buffer/WAL counting during abort
SET pg_stat_statements.track = 'all';
CREATE TEMP TABLE pgss_call_tab (a int, b char(20));
CREATE TEMP TABLE pgss_call_tab2 (a int, b char(20));
INSERT INTO pgss_call_tab VALUES (0, 'zzz');
CREATE PROCEDURE pgss_call_rollback_proc() AS $$
DECLARE
v int;
BEGIN
EXPLAIN ANALYZE WITH ins AS (INSERT INTO pgss_call_tab2 SELECT * FROM pgss_call_tab RETURNING a)
SELECT a / 0 INTO v FROM ins;
EXCEPTION WHEN division_by_zero THEN
END;
$$ LANGUAGE plpgsql;
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
---
t
(1 row)

CALL pgss_call_rollback_proc();
SELECT query, calls,
local_blks_hit + local_blks_read > 0 as local_hitread,
wal_bytes > 0 as wal_bytes_generated,
wal_records > 0 as wal_records_generated
FROM pg_stat_statements
WHERE query LIKE '%pgss_call_rollback_proc%'
ORDER BY query COLLATE "C";
query | calls | local_hitread | wal_bytes_generated | wal_records_generated
--------------------------------+-------+---------------+---------------------+-----------------------
CALL pgss_call_rollback_proc() | 1 | t | t | t
(1 row)

DROP TABLE pgss_call_tab2;
DROP TABLE pgss_call_tab;
DROP PROCEDURE pgss_call_rollback_proc;
SET pg_stat_statements.track = 'top';
-- CALL
CREATE OR REPLACE PROCEDURE sum_one(i int) AS $$
DECLARE
Expand Down
48 changes: 48 additions & 0 deletions contrib/pg_stat_statements/expected/wal.out
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,51 @@ SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
(1 row)

--
-- Validate buffer/WAL counting with caught exception in PL/pgSQL
--
CREATE TEMP TABLE pgss_error_tab (a int, b char(20));
INSERT INTO pgss_error_tab VALUES (0, 'zzz');
CREATE FUNCTION pgss_error_func() RETURNS void AS $$
DECLARE
v int;
BEGIN
WITH ins AS (INSERT INTO pgss_error_tab VALUES (1, 'aaa') RETURNING a)
SELECT a / 0 INTO v FROM ins;
EXCEPTION WHEN division_by_zero THEN
NULL;
END;
$$ LANGUAGE plpgsql;
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
---
t
(1 row)

SELECT pgss_error_func();
pgss_error_func
-----------------

(1 row)

-- Buffer/WAL usage from the wCTE INSERT should survive the exception
SELECT query, calls,
local_blks_hit + local_blks_read > 0 as local_hitread,
wal_bytes > 0 as wal_bytes_generated,
wal_records > 0 as wal_records_generated
FROM pg_stat_statements
WHERE query LIKE '%pgss_error_func%'
ORDER BY query COLLATE "C";
query | calls | local_hitread | wal_bytes_generated | wal_records_generated
--------------------------+-------+---------------+---------------------+-----------------------
SELECT pgss_error_func() | 1 | t | t | t
(1 row)

DROP TABLE pgss_error_tab;
DROP FUNCTION pgss_error_func;
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
---
t
(1 row)

64 changes: 16 additions & 48 deletions contrib/pg_stat_statements/pg_stat_statements.c
Original file line number Diff line number Diff line change
Expand Up @@ -900,22 +900,16 @@ pgss_planner(Query *parse,
&& pgss_track_planning && query_string
&& parse->queryId != INT64CONST(0))
{
instr_time start;
instr_time duration;
BufferUsage bufusage_start,
bufusage;
WalUsage walusage_start,
walusage;

/* We need to track buffer usage as the planner can access them. */
bufusage_start = pgBufferUsage;
Instrumentation instr = {0};

/*
* We need to track buffer usage as the planner can access them.
*
* Similarly the planner could write some WAL records in some cases
* (e.g. setting a hint bit with those being WAL-logged)
*/
walusage_start = pgWalUsage;
INSTR_TIME_SET_CURRENT(start);
InstrInitOptions(&instr, INSTRUMENT_ALL);
InstrStart(&instr);

nesting_level++;
PG_TRY();
Expand All @@ -929,30 +923,20 @@ pgss_planner(Query *parse,
}
PG_FINALLY();
{
InstrStopFinalize(&instr);
nesting_level--;
}
PG_END_TRY();

INSTR_TIME_SET_CURRENT(duration);
INSTR_TIME_SUBTRACT(duration, start);

/* calc differences of buffer counters. */
memset(&bufusage, 0, sizeof(BufferUsage));
BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start);

/* calc differences of WAL counters. */
memset(&walusage, 0, sizeof(WalUsage));
WalUsageAccumDiff(&walusage, &pgWalUsage, &walusage_start);

pgss_store(query_string,
parse->queryId,
parse->stmt_location,
parse->stmt_len,
PGSS_PLAN,
INSTR_TIME_GET_MILLISEC(duration),
INSTR_TIME_GET_MILLISEC(instr.total),
0,
&bufusage,
&walusage,
&instr.bufusage,
&instr.walusage,
NULL,
NULL,
0,
Expand Down Expand Up @@ -1135,17 +1119,11 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
!IsA(parsetree, ExecuteStmt) &&
!IsA(parsetree, PrepareStmt))
{
instr_time start;
instr_time duration;
uint64 rows;
BufferUsage bufusage_start,
bufusage;
WalUsage walusage_start,
walusage;
Instrumentation instr = {0};

bufusage_start = pgBufferUsage;
walusage_start = pgWalUsage;
INSTR_TIME_SET_CURRENT(start);
InstrInitOptions(&instr, INSTRUMENT_ALL);
InstrStart(&instr);

nesting_level++;
PG_TRY();
Expand All @@ -1161,6 +1139,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
}
PG_FINALLY();
{
InstrStopFinalize(&instr);
nesting_level--;
}
PG_END_TRY();
Expand All @@ -1176,9 +1155,6 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
*/
pstmt = NULL;

INSTR_TIME_SET_CURRENT(duration);
INSTR_TIME_SUBTRACT(duration, start);

/*
* Track the total number of rows retrieved or affected by the utility
* statements of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED
Expand All @@ -1190,23 +1166,15 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
qc->commandTag == CMDTAG_REFRESH_MATERIALIZED_VIEW)) ?
qc->nprocessed : 0;

/* calc differences of buffer counters. */
memset(&bufusage, 0, sizeof(BufferUsage));
BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start);

/* calc differences of WAL counters. */
memset(&walusage, 0, sizeof(WalUsage));
WalUsageAccumDiff(&walusage, &pgWalUsage, &walusage_start);

pgss_store(queryString,
saved_queryId,
saved_stmt_location,
saved_stmt_len,
PGSS_EXEC,
INSTR_TIME_GET_MILLISEC(duration),
INSTR_TIME_GET_MILLISEC(instr.total),
rows,
&bufusage,
&walusage,
&instr.bufusage,
&instr.walusage,
NULL,
NULL,
0,
Expand Down
56 changes: 56 additions & 0 deletions contrib/pg_stat_statements/sql/utility.sql
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,62 @@ EXPLAIN (costs off) SELECT a FROM generate_series(1,10) AS tab(a) WHERE a = 7;

SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";

-- Buffer stats should flow through EXPLAIN ANALYZE
CREATE TEMP TABLE flow_through_test (a int, b char(200));
INSERT INTO flow_through_test SELECT i, repeat('x', 200) FROM generate_series(1, 5000) AS i;

CREATE FUNCTION run_explain_buffers_test() RETURNS void AS $$
DECLARE
BEGIN
EXECUTE 'EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM flow_through_test';
END;
$$ LANGUAGE plpgsql;

SELECT pg_stat_statements_reset() IS NOT NULL AS t;

SELECT run_explain_buffers_test();

-- EXPLAIN entries should have non-zero buffer stats
SELECT query, local_blks_hit + local_blks_read > 0 as has_buffer_stats
FROM pg_stat_statements
WHERE query LIKE 'SELECT run_explain_buffers_test%'
ORDER BY query COLLATE "C";

DROP FUNCTION run_explain_buffers_test;
DROP TABLE flow_through_test;

-- Validate buffer/WAL counting during abort
SET pg_stat_statements.track = 'all';
CREATE TEMP TABLE pgss_call_tab (a int, b char(20));
CREATE TEMP TABLE pgss_call_tab2 (a int, b char(20));
INSERT INTO pgss_call_tab VALUES (0, 'zzz');

CREATE PROCEDURE pgss_call_rollback_proc() AS $$
DECLARE
v int;
BEGIN
EXPLAIN ANALYZE WITH ins AS (INSERT INTO pgss_call_tab2 SELECT * FROM pgss_call_tab RETURNING a)
SELECT a / 0 INTO v FROM ins;
EXCEPTION WHEN division_by_zero THEN
END;
$$ LANGUAGE plpgsql;

SELECT pg_stat_statements_reset() IS NOT NULL AS t;
CALL pgss_call_rollback_proc();

SELECT query, calls,
local_blks_hit + local_blks_read > 0 as local_hitread,
wal_bytes > 0 as wal_bytes_generated,
wal_records > 0 as wal_records_generated
FROM pg_stat_statements
WHERE query LIKE '%pgss_call_rollback_proc%'
ORDER BY query COLLATE "C";

DROP TABLE pgss_call_tab2;
DROP TABLE pgss_call_tab;
DROP PROCEDURE pgss_call_rollback_proc;
SET pg_stat_statements.track = 'top';

-- CALL
CREATE OR REPLACE PROCEDURE sum_one(i int) AS $$
DECLARE
Expand Down
33 changes: 33 additions & 0 deletions contrib/pg_stat_statements/sql/wal.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,36 @@ wal_records > 0 as wal_records_generated,
wal_records >= rows as wal_records_ge_rows
FROM pg_stat_statements ORDER BY query COLLATE "C";
SELECT pg_stat_statements_reset() IS NOT NULL AS t;

--
-- Validate buffer/WAL counting with caught exception in PL/pgSQL
--
CREATE TEMP TABLE pgss_error_tab (a int, b char(20));
INSERT INTO pgss_error_tab VALUES (0, 'zzz');

CREATE FUNCTION pgss_error_func() RETURNS void AS $$
DECLARE
v int;
BEGIN
WITH ins AS (INSERT INTO pgss_error_tab VALUES (1, 'aaa') RETURNING a)
SELECT a / 0 INTO v FROM ins;
EXCEPTION WHEN division_by_zero THEN
NULL;
END;
$$ LANGUAGE plpgsql;

SELECT pg_stat_statements_reset() IS NOT NULL AS t;
SELECT pgss_error_func();

-- Buffer/WAL usage from the wCTE INSERT should survive the exception
SELECT query, calls,
local_blks_hit + local_blks_read > 0 as local_hitread,
wal_bytes > 0 as wal_bytes_generated,
wal_records > 0 as wal_records_generated
FROM pg_stat_statements
WHERE query LIKE '%pgss_error_func%'
ORDER BY query COLLATE "C";

DROP TABLE pgss_error_tab;
DROP FUNCTION pgss_error_func;
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
Loading
Loading