diff --git a/contrib/pg_stat_statements/expected/dml.out b/contrib/pg_stat_statements/expected/dml.out index 347cb8699e4..80ae1a5bc06 100644 --- a/contrib/pg_stat_statements/expected/dml.out +++ b/contrib/pg_stat_statements/expected/dml.out @@ -172,3 +172,24 @@ SELECT pg_stat_statements_reset() IS NOT NULL AS t; t (1 row) +-- aborted calls tracking +SELECT pg_sleep(0.5); + pg_sleep +---------- + +(1 row) + +SET statement_timeout = '50ms'; +SELECT pg_sleep(0.5); +ERROR: canceling statement due to statement timeout +SELECT pg_sleep(0.5), 'test'; +ERROR: canceling statement due to statement timeout +SET statement_timeout = '0'; +SELECT query, calls, calls_aborted FROM pg_stat_statements +WHERE query LIKE '%pg_sleep%' ORDER BY query COLLATE "C"; + query | calls | calls_aborted +-------------------------+-------+--------------- + SELECT pg_sleep($1) | 1 | 1 + SELECT pg_sleep($1), $2 | 0 | 1 +(2 rows) + diff --git a/contrib/pg_stat_statements/expected/oldextversions.out b/contrib/pg_stat_statements/expected/oldextversions.out index 726383a99d7..d5ebb59d5a1 100644 --- a/contrib/pg_stat_statements/expected/oldextversions.out +++ b/contrib/pg_stat_statements/expected/oldextversions.out @@ -425,6 +425,7 @@ AlTER EXTENSION pg_stat_statements UPDATE TO '1.13'; mean_plan_time | double precision | | | stddev_plan_time | double precision | | | calls | bigint | | | + calls_aborted | bigint | | | total_exec_time | double precision | | | min_exec_time | double precision | | | max_exec_time | double precision | | | diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.12--1.13.sql b/contrib/pg_stat_statements/pg_stat_statements--1.12--1.13.sql index 2f0eaf14ec3..fe8bcb6398b 100644 --- a/contrib/pg_stat_statements/pg_stat_statements--1.12--1.13.sql +++ b/contrib/pg_stat_statements/pg_stat_statements--1.12--1.13.sql @@ -25,6 +25,7 @@ CREATE FUNCTION pg_stat_statements(IN showtext boolean, OUT mean_plan_time float8, OUT stddev_plan_time float8, OUT calls int8, + OUT calls_aborted int8, OUT total_exec_time float8, OUT min_exec_time float8, OUT max_exec_time float8, diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 9fc9635d330..61cd1fe6bb7 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -98,7 +98,7 @@ static const uint32 PGSS_PG_MAJOR_VERSION = PG_VERSION_NUM / 100; #define USAGE_DECREASE_FACTOR (0.99) /* decreased every entry_dealloc */ #define STICKY_DECREASE_FACTOR (0.50) /* factor for sticky entries */ #define USAGE_DEALLOC_PERCENT 5 /* free this % of entries at once */ -#define IS_STICKY(c) ((c.calls[PGSS_PLAN] + c.calls[PGSS_EXEC]) == 0) +#define IS_STICKY(c) ((c.calls[PGSS_PLAN] + c.calls[PGSS_EXEC] + c.calls_aborted) == 0) /* * Extension version number, for supporting older extension versions' objects @@ -155,6 +155,9 @@ typedef struct pgssHashKey typedef struct Counters { int64 calls[PGSS_NUMKIND]; /* # of times planned/executed */ + + int64 calls_aborted; /* # of times query was aborted */ + double total_time[PGSS_NUMKIND]; /* total planning/execution time, * in msec */ double min_time[PGSS_NUMKIND]; /* minimum planning/execution time in @@ -1033,6 +1036,8 @@ pgss_ExecutorStart(QueryDesc *queryDesc, int eflags) static void pgss_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count) { + volatile bool query_completed = false; + nesting_level++; PG_TRY(); { @@ -1040,9 +1045,52 @@ pgss_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count) prev_ExecutorRun(queryDesc, direction, count); else standard_ExecutorRun(queryDesc, direction, count); + + /* Mark as completed if we reach here */ + query_completed = true; } PG_FINALLY(); { + /* + * Check if tracking was enabled when ExecutorRun started. + * We use (nesting_level - 1) because nesting_level was incremented + * at the start of this function, but the tracking decision should + * be based on the level when the query began execution. + * This is crucial for PGSS_TRACK_TOP mode to work correctly. + */ + bool was_enabled = pgss_enabled(nesting_level - 1); + + if (!query_completed && pgss && was_enabled && + queryDesc->plannedstmt->queryId != UINT64CONST(0)) + { + pgssHashKey key; + pgssEntry *entry; + + /* Clear padding to ensure proper hash key comparison */ + memset(&key, 0, sizeof(pgssHashKey)); + + key.userid = GetUserId(); + key.dbid = MyDatabaseId; + /* nesting_level was incremented at start of ExecutorRun */ + key.toplevel = (nesting_level == 1); + key.queryid = queryDesc->plannedstmt->queryId; + + LWLockAcquire(pgss->lock, LW_EXCLUSIVE); + + /* Only increment calls_aborted if entry already exists. + * Entries are created in pgss_post_parse_analyze for queries with constants. + * If no entry exists, the query wouldn't normally be tracked anyway. */ + entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL); + + if (entry) + { + SpinLockAcquire(&entry->mutex); + entry->counters.calls_aborted++; + SpinLockRelease(&entry->mutex); + } + LWLockRelease(pgss->lock); + } + nesting_level--; } PG_END_TRY(); @@ -1578,8 +1626,8 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS) #define PG_STAT_STATEMENTS_COLS_V1_10 43 #define PG_STAT_STATEMENTS_COLS_V1_11 49 #define PG_STAT_STATEMENTS_COLS_V1_12 52 -#define PG_STAT_STATEMENTS_COLS_V1_13 54 -#define PG_STAT_STATEMENTS_COLS 54 /* maximum of above */ +#define PG_STAT_STATEMENTS_COLS_V1_13 55 +#define PG_STAT_STATEMENTS_COLS 55 /* maximum of above */ /* * Retrieve statement statistics. @@ -1922,6 +1970,13 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo, if (kind == PGSS_EXEC || api_version >= PGSS_V1_8) { values[i++] = Int64GetDatumFast(tmp.calls[kind]); + + /* Add calls_aborted right after execution calls */ + if (kind == PGSS_EXEC && api_version >= PGSS_V1_13) + { + values[i++] = Int64GetDatumFast(tmp.calls_aborted); + } + values[i++] = Float8GetDatumFast(tmp.total_time[kind]); } diff --git a/contrib/pg_stat_statements/sql/dml.sql b/contrib/pg_stat_statements/sql/dml.sql index 9986b0a22d3..199f018ebc3 100644 --- a/contrib/pg_stat_statements/sql/dml.sql +++ b/contrib/pg_stat_statements/sql/dml.sql @@ -93,3 +93,13 @@ SELECT FROM pg_stat_statements; SELECT pg_stat_statements_reset() IS NOT NULL AS t; + +-- aborted calls tracking +SELECT pg_sleep(0.5); +SET statement_timeout = '50ms'; +SELECT pg_sleep(0.5); +SELECT pg_sleep(0.5), 'test'; +SET statement_timeout = '0'; + +SELECT query, calls, calls_aborted FROM pg_stat_statements +WHERE query LIKE '%pg_sleep%' ORDER BY query COLLATE "C";