Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2d3490d
heapam: Keep buffer pins across index scan resets.
petergeoghegan Apr 4, 2026
33bf731
Make index_concurrently_create_copy more general
alvherre Apr 4, 2026
69c11f0
Modernize struct declarations in snapbuild.h
alvherre Apr 4, 2026
2849fe4
Fix unused function warning on Arm platforms
j-naylor Apr 5, 2026
a9ee668
aio: io_uring: Trigger async processing for large IOs
anarazel Apr 5, 2026
434dab7
read_stream: Move logic about IO combining & issuing to helpers
anarazel Apr 5, 2026
8ca147d
read stream: Split decision about look ahead for AIO and combining
anarazel Apr 5, 2026
f63ca33
read_stream: Only increase read-ahead distance when waiting for IO
anarazel Apr 5, 2026
fc44f10
aio: Simplify pgaio_worker_submit().
macdice Apr 5, 2026
de28140
postgres_fdw: Inherit the local transaction's access/deferrable modes.
Apr 5, 2026
caec9d9
Allow index_create to suppress index_build progress reporting
alvherre Apr 5, 2026
9134b82
instrumentation: Separate trigger logic from other uses
lfittl Mar 2, 2025
bf62064
instrumentation: Separate per-node logic from other uses
lfittl Mar 7, 2026
1b84f12
instrumentation: Use Instrumentation instead of manual buffer tracking
lfittl Apr 5, 2026
5fc2d25
instrumentation: Replace direct changes of pgBufferUsage/pgWalUsage w…
lfittl Mar 27, 2026
11c9364
Parallel Bitmap Heap Scan: Fix EXPLAIN reporting of "Heap Blocks"
lfittl Apr 5, 2026
eb3be81
Add regression test coverage for EXPLAIN of Parallel Index Only Scans
lfittl Apr 5, 2026
73d3d43
instrumentation: Add additional regression tests covering buffer usage
lfittl Apr 5, 2026
9386bc7
Optimize measuring WAL/buffer usage through stack-based instrumentation
lfittl Sep 9, 2025
9365bbd
instrumentation: Use Instrumentation struct for parallel workers
lfittl Mar 16, 2026
d51ed5a
instrumentation: Optimize ExecProcNodeInstr instructions by inlining
lfittl Mar 8, 2026
b57c411
Index scans: Show table buffer accesses separately in EXPLAIN ANALYZE
lfittl Mar 7, 2026
ff71ea6
Add test_session_buffer_usage test module
lfittl Mar 7, 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
22 changes: 3 additions & 19 deletions contrib/auto_explain/auto_explain.c
Original file line number Diff line number Diff line change
Expand Up @@ -305,19 +305,9 @@ explain_ExecutorStart(QueryDesc *queryDesc, int eflags)

if (auto_explain_enabled())
{
/*
* Set up to track total elapsed time in ExecutorRun. Make sure the
* space is allocated in the per-query context so it will go away at
* ExecutorEnd.
*/
/* Set up to track total elapsed time in ExecutorRun. */
if (queryDesc->totaltime == NULL)
{
MemoryContext oldcxt;

oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt);
queryDesc->totaltime = InstrAlloc(1, INSTRUMENT_ALL, false);
MemoryContextSwitchTo(oldcxt);
}
queryDesc->totaltime = InstrQueryAlloc(INSTRUMENT_ALL);
}
}

Expand Down Expand Up @@ -381,14 +371,8 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
*/
oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt);

/*
* Make sure stats accumulation is done. (Note: it's okay if several
* levels of hook all do this.)
*/
InstrEndLoop(queryDesc->totaltime);

/* Log plan if duration is exceeded. */
msec = INSTR_TIME_GET_MILLISEC(queryDesc->totaltime->total);
msec = INSTR_TIME_GET_MILLISEC(queryDesc->totaltime->instr.total);
if (msec >= auto_explain_log_min_duration)
{
ExplainState *es = NewExplainState();
Expand Down
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)

93 changes: 20 additions & 73 deletions contrib/pg_stat_statements/pg_stat_statements.c
Original file line number Diff line number Diff line change
Expand Up @@ -911,22 +911,11 @@ 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;
Instrumentation instr = {0};

/* We need to track buffer usage as the planner can access them. */
bufusage_start = pgBufferUsage;

/*
* 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);
/* Track time and buffer/WAL usage as the planner can access them. */
InstrInitOptions(&instr, INSTRUMENT_ALL);
InstrStart(&instr);

nesting_level++;
PG_TRY();
Expand All @@ -940,30 +929,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 @@ -1015,19 +994,9 @@ pgss_ExecutorStart(QueryDesc *queryDesc, int eflags)
*/
if (pgss_enabled(nesting_level) && queryDesc->plannedstmt->queryId != INT64CONST(0))
{
/*
* Set up to track total elapsed time in ExecutorRun. Make sure the
* space is allocated in the per-query context so it will go away at
* ExecutorEnd.
*/
/* Set up to track total elapsed time in ExecutorRun. */
if (queryDesc->totaltime == NULL)
{
MemoryContext oldcxt;

oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt);
queryDesc->totaltime = InstrAlloc(1, INSTRUMENT_ALL, false);
MemoryContextSwitchTo(oldcxt);
}
queryDesc->totaltime = InstrQueryAlloc(INSTRUMENT_ALL);
}
}

Expand Down Expand Up @@ -1084,21 +1053,15 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
if (queryId != INT64CONST(0) && queryDesc->totaltime &&
pgss_enabled(nesting_level))
{
/*
* Make sure stats accumulation is done. (Note: it's okay if several
* levels of hook all do this.)
*/
InstrEndLoop(queryDesc->totaltime);

pgss_store(queryDesc->sourceText,
queryId,
queryDesc->plannedstmt->stmt_location,
queryDesc->plannedstmt->stmt_len,
PGSS_EXEC,
INSTR_TIME_GET_MILLISEC(queryDesc->totaltime->total),
INSTR_TIME_GET_MILLISEC(queryDesc->totaltime->instr.total),
queryDesc->estate->es_total_processed,
&queryDesc->totaltime->bufusage,
&queryDesc->totaltime->walusage,
&queryDesc->totaltime->instr.bufusage,
&queryDesc->totaltime->instr.walusage,
queryDesc->estate->es_jit ? &queryDesc->estate->es_jit->instr : NULL,
NULL,
queryDesc->estate->es_parallel_workers_to_launch,
Expand Down Expand Up @@ -1162,17 +1125,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 @@ -1188,6 +1145,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
}
PG_FINALLY();
{
InstrStopFinalize(&instr);
nesting_level--;
}
PG_END_TRY();
Expand All @@ -1202,9 +1160,6 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
* former value, which'd otherwise be a good idea.
*/

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 @@ -1216,23 +1171,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
Loading