Skip to content

Commit 0f4c486

Browse files
committed
pg_stat_statements: Add rows_filtered column
Add a rows_filtered column to pg_stat_statements to track rows removed by scan/join/other filter conditions. This metric helps identify queries that may benefit from better indexing. The implementation: - Enables per-node instrumentation with INSTRUMENT_ALL before ExecutorStart - Walks the plan tree in ExecutorEnd to sum nfiltered1 (scanqual/joinqual) and nfiltered2 (other quals) from all nodes - Reads both ntuples and tuplecount to capture complete tuple counts from both completed and current execution cycles - Includes the new column in the SQL function and view for version 1.15
1 parent 4739a69 commit 0f4c486

File tree

5 files changed

+159
-9
lines changed

5 files changed

+159
-9
lines changed

contrib/pg_stat_statements/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ OBJS = \
77

88
EXTENSION = pg_stat_statements
99
DATA = pg_stat_statements--1.4.sql \
10+
pg_stat_statements--1.14--1.15.sql \
1011
pg_stat_statements--1.13--1.14.sql \
1112
pg_stat_statements--1.12--1.13.sql \
1213
pg_stat_statements--1.11--1.12.sql pg_stat_statements--1.10--1.11.sql \

contrib/pg_stat_statements/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ contrib_targets += pg_stat_statements
2121
install_data(
2222
'pg_stat_statements.control',
2323
'pg_stat_statements--1.4.sql',
24+
'pg_stat_statements--1.14--1.15.sql',
2425
'pg_stat_statements--1.13--1.14.sql',
2526
'pg_stat_statements--1.12--1.13.sql',
2627
'pg_stat_statements--1.11--1.12.sql',
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/* contrib/pg_stat_statements/pg_stat_statements--1.14--1.15.sql */
2+
3+
-- complain if script is sourced in psql, rather than via ALTER EXTENSION
4+
\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.15'" to load this file. \quit
5+
6+
/* First we have to remove them from the extension */
7+
ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements;
8+
ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean);
9+
10+
/* Then we can drop them */
11+
DROP VIEW pg_stat_statements;
12+
DROP FUNCTION pg_stat_statements(boolean);
13+
14+
/* Now redefine */
15+
CREATE FUNCTION pg_stat_statements(IN showtext boolean,
16+
OUT userid oid,
17+
OUT dbid oid,
18+
OUT toplevel bool,
19+
OUT queryid bigint,
20+
OUT query text,
21+
OUT plans int8,
22+
OUT total_plan_time float8,
23+
OUT min_plan_time float8,
24+
OUT max_plan_time float8,
25+
OUT mean_plan_time float8,
26+
OUT stddev_plan_time float8,
27+
OUT calls int8,
28+
OUT total_exec_time float8,
29+
OUT min_exec_time float8,
30+
OUT max_exec_time float8,
31+
OUT mean_exec_time float8,
32+
OUT stddev_exec_time float8,
33+
OUT rows int8,
34+
OUT rows_scanned int8,
35+
OUT rows_filtered int8,
36+
OUT shared_blks_hit int8,
37+
OUT shared_blks_read int8,
38+
OUT shared_blks_dirtied int8,
39+
OUT shared_blks_written int8,
40+
OUT local_blks_hit int8,
41+
OUT local_blks_read int8,
42+
OUT local_blks_dirtied int8,
43+
OUT local_blks_written int8,
44+
OUT temp_blks_read int8,
45+
OUT temp_blks_written int8,
46+
OUT shared_blk_read_time float8,
47+
OUT shared_blk_write_time float8,
48+
OUT local_blk_read_time float8,
49+
OUT local_blk_write_time float8,
50+
OUT temp_blk_read_time float8,
51+
OUT temp_blk_write_time float8,
52+
OUT wal_records int8,
53+
OUT wal_fpi int8,
54+
OUT wal_bytes numeric,
55+
OUT wal_buffers_full int8,
56+
OUT jit_functions int8,
57+
OUT jit_generation_time float8,
58+
OUT jit_inlining_count int8,
59+
OUT jit_inlining_time float8,
60+
OUT jit_optimization_count int8,
61+
OUT jit_optimization_time float8,
62+
OUT jit_emission_count int8,
63+
OUT jit_emission_time float8,
64+
OUT jit_deform_count int8,
65+
OUT jit_deform_time float8,
66+
OUT parallel_workers_to_launch int8,
67+
OUT parallel_workers_launched int8,
68+
OUT generic_plan_calls int8,
69+
OUT custom_plan_calls int8,
70+
OUT stats_since timestamp with time zone,
71+
OUT minmax_stats_since timestamp with time zone
72+
)
73+
RETURNS SETOF record
74+
AS 'MODULE_PATHNAME', 'pg_stat_statements_1_15'
75+
LANGUAGE C STRICT VOLATILE PARALLEL SAFE;
76+
77+
CREATE VIEW pg_stat_statements AS
78+
SELECT * FROM pg_stat_statements(true);
79+
80+
GRANT SELECT ON pg_stat_statements TO PUBLIC;

contrib/pg_stat_statements/pg_stat_statements.c

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ typedef enum pgssVersion
118118
PGSS_V1_12,
119119
PGSS_V1_13,
120120
PGSS_V1_14,
121+
PGSS_V1_15,
121122
} pgssVersion;
122123

123124
typedef enum pgssStoreKind
@@ -169,6 +170,7 @@ typedef struct Counters
169170
* planning/execution time in msec */
170171
int64 rows; /* total # of retrieved or affected rows */
171172
int64 rows_scanned; /* total # of rows scanned by scan nodes */
173+
int64 rows_filtered; /* total # of rows filtered out by quals */
172174
int64 shared_blks_hit; /* # of shared buffer hits */
173175
int64 shared_blks_read; /* # of shared disk blocks read */
174176
int64 shared_blks_dirtied; /* # of shared disk blocks dirtied */
@@ -331,6 +333,7 @@ PG_FUNCTION_INFO_V1(pg_stat_statements_1_11);
331333
PG_FUNCTION_INFO_V1(pg_stat_statements_1_12);
332334
PG_FUNCTION_INFO_V1(pg_stat_statements_1_13);
333335
PG_FUNCTION_INFO_V1(pg_stat_statements_1_14);
336+
PG_FUNCTION_INFO_V1(pg_stat_statements_1_15);
334337
PG_FUNCTION_INFO_V1(pg_stat_statements);
335338
PG_FUNCTION_INFO_V1(pg_stat_statements_info);
336339

@@ -360,6 +363,7 @@ static void pgss_store(const char *query, int64 queryId,
360363
pgssStoreKind kind,
361364
double total_time, uint64 rows,
362365
int64 rows_scanned,
366+
int64 rows_filtered,
363367
const BufferUsage *bufusage,
364368
const WalUsage *walusage,
365369
const struct JitInstrumentation *jitusage,
@@ -884,6 +888,7 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate)
884888
0,
885889
0,
886890
0,
891+
0,
887892
NULL,
888893
NULL,
889894
NULL,
@@ -967,6 +972,7 @@ pgss_planner(Query *parse,
967972
INSTR_TIME_GET_MILLISEC(duration),
968973
0,
969974
0,
975+
0,
970976
&bufusage,
971977
&walusage,
972978
NULL,
@@ -1008,6 +1014,20 @@ pgss_planner(Query *parse,
10081014
static void
10091015
pgss_ExecutorStart(QueryDesc *queryDesc, int eflags)
10101016
{
1017+
/*
1018+
* If we're going to track this query, ensure per-node instrumentation is
1019+
* enabled so we can collect rows_scanned and rows_filtered statistics.
1020+
* This must be done before calling standard_ExecutorStart, which is when
1021+
* the instrumentation structures are allocated for each plan node.
1022+
*
1023+
* We use INSTRUMENT_ALL to ensure full instrumentation, including timing
1024+
* which is needed for proper tuple counting in some code paths.
1025+
*/
1026+
if (pgss_enabled(nesting_level) && queryDesc->plannedstmt->queryId != INT64CONST(0))
1027+
{
1028+
queryDesc->instrument_options |= INSTRUMENT_ALL;
1029+
}
1030+
10111031
if (prev_ExecutorStart)
10121032
prev_ExecutorStart(queryDesc, eflags);
10131033
else
@@ -1084,6 +1104,7 @@ pgss_ExecutorFinish(QueryDesc *queryDesc)
10841104
typedef struct ScanStats
10851105
{
10861106
int64 rows_scanned; /* total rows scanned (before filtering) */
1107+
int64 rows_filtered; /* total rows filtered by all nodes */
10871108
} ScanStats;
10881109

10891110
/*
@@ -1113,23 +1134,48 @@ IsScanNode(PlanState *planstate)
11131134
/*
11141135
* Walker function to collect scan statistics from all nodes.
11151136
* For scan nodes, rows_scanned = tuples output + tuples filtered.
1137+
* For all nodes, rows_filtered = sum of nfiltered1 + nfiltered2.
11161138
*/
11171139
static bool
11181140
pgss_collect_scan_stats_walker(PlanState *planstate, void *context)
11191141
{
11201142
ScanStats *stats = (ScanStats *) context;
11211143

1122-
if (planstate->instrument && IsScanNode(planstate))
1144+
if (planstate->instrument)
11231145
{
1146+
Instrumentation *instr = planstate->instrument;
1147+
11241148
/*
1125-
* For scan nodes, rows_scanned is the number of tuples produced plus
1126-
* the number of tuples filtered out by the scan's filter condition.
1127-
* This represents the total number of tuples read from storage.
1149+
* Get the total tuple count for this node. The tuples are tracked in
1150+
* two places: 'ntuples' holds the count from completed cycles, and
1151+
* 'tuplecount' holds the count from the current (possibly incomplete)
1152+
* cycle. We need both to get an accurate total.
1153+
*
1154+
* Note: InstrEndLoop() would normally move tuplecount to ntuples, but
1155+
* it returns early if 'running' is false (which happens after each
1156+
* InstrStopNode call), so we can't rely on it here.
11281157
*/
1129-
double scanned = planstate->instrument->ntuples +
1130-
planstate->instrument->nfiltered1;
1158+
double node_tuples = instr->ntuples + instr->tuplecount;
11311159

1132-
stats->rows_scanned += (int64) scanned;
1160+
/*
1161+
* Collect rows_filtered from all nodes. nfiltered1 tracks tuples
1162+
* removed by scanqual or joinqual, nfiltered2 tracks tuples removed
1163+
* by "other" quals.
1164+
*/
1165+
stats->rows_filtered += (int64) (instr->nfiltered1 + instr->nfiltered2);
1166+
1167+
if (IsScanNode(planstate))
1168+
{
1169+
/*
1170+
* For scan nodes, rows_scanned is the number of tuples produced
1171+
* plus the number of tuples filtered out by the scan's filter
1172+
* condition. This represents the total number of tuples read
1173+
* from storage.
1174+
*/
1175+
double scanned = node_tuples + instr->nfiltered1;
1176+
1177+
stats->rows_scanned += (int64) scanned;
1178+
}
11331179
}
11341180

11351181
return planstate_tree_walker(planstate, pgss_collect_scan_stats_walker, context);
@@ -1179,6 +1225,7 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
11791225
queryDesc->totaltime->total * 1000.0, /* convert to msec */
11801226
queryDesc->estate->es_total_processed,
11811227
scan_stats.rows_scanned,
1228+
scan_stats.rows_filtered,
11821229
&queryDesc->totaltime->bufusage,
11831230
&queryDesc->totaltime->walusage,
11841231
queryDesc->estate->es_jit ? &queryDesc->estate->es_jit->instr : NULL,
@@ -1314,6 +1361,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
13141361
INSTR_TIME_GET_MILLISEC(duration),
13151362
rows,
13161363
0, /* rows_scanned not available for utility statements */
1364+
0, /* rows_filtered not available for utility statements */
13171365
&bufusage,
13181366
&walusage,
13191367
NULL,
@@ -1379,6 +1427,7 @@ pgss_store(const char *query, int64 queryId,
13791427
pgssStoreKind kind,
13801428
double total_time, uint64 rows,
13811429
int64 rows_scanned,
1430+
int64 rows_filtered,
13821431
const BufferUsage *bufusage,
13831432
const WalUsage *walusage,
13841433
const struct JitInstrumentation *jitusage,
@@ -1547,6 +1596,7 @@ pgss_store(const char *query, int64 queryId,
15471596
}
15481597
entry->counters.rows += rows;
15491598
entry->counters.rows_scanned += rows_scanned;
1599+
entry->counters.rows_filtered += rows_filtered;
15501600
entry->counters.shared_blks_hit += bufusage->shared_blks_hit;
15511601
entry->counters.shared_blks_read += bufusage->shared_blks_read;
15521602
entry->counters.shared_blks_dirtied += bufusage->shared_blks_dirtied;
@@ -1669,7 +1719,8 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
16691719
#define PG_STAT_STATEMENTS_COLS_V1_12 52
16701720
#define PG_STAT_STATEMENTS_COLS_V1_13 54
16711721
#define PG_STAT_STATEMENTS_COLS_V1_14 55
1672-
#define PG_STAT_STATEMENTS_COLS 55 /* maximum of above */
1722+
#define PG_STAT_STATEMENTS_COLS_V1_15 56
1723+
#define PG_STAT_STATEMENTS_COLS 56 /* maximum of above */
16731724

16741725
/*
16751726
* Retrieve statement statistics.
@@ -1681,6 +1732,16 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
16811732
* expected API version is identified by embedding it in the C name of the
16821733
* function. Unfortunately we weren't bright enough to do that for 1.1.
16831734
*/
1735+
Datum
1736+
pg_stat_statements_1_15(PG_FUNCTION_ARGS)
1737+
{
1738+
bool showtext = PG_GETARG_BOOL(0);
1739+
1740+
pg_stat_statements_internal(fcinfo, PGSS_V1_15, showtext);
1741+
1742+
return (Datum) 0;
1743+
}
1744+
16841745
Datum
16851746
pg_stat_statements_1_14(PG_FUNCTION_ARGS)
16861747
{
@@ -1867,6 +1928,10 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
18671928
if (api_version != PGSS_V1_14)
18681929
elog(ERROR, "incorrect number of output arguments");
18691930
break;
1931+
case PG_STAT_STATEMENTS_COLS_V1_15:
1932+
if (api_version != PGSS_V1_15)
1933+
elog(ERROR, "incorrect number of output arguments");
1934+
break;
18701935
default:
18711936
elog(ERROR, "incorrect number of output arguments");
18721937
}
@@ -2052,6 +2117,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
20522117
values[i++] = Int64GetDatumFast(tmp.rows);
20532118
if (api_version >= PGSS_V1_14)
20542119
values[i++] = Int64GetDatumFast(tmp.rows_scanned);
2120+
if (api_version >= PGSS_V1_15)
2121+
values[i++] = Int64GetDatumFast(tmp.rows_filtered);
20552122
values[i++] = Int64GetDatumFast(tmp.shared_blks_hit);
20562123
values[i++] = Int64GetDatumFast(tmp.shared_blks_read);
20572124
if (api_version >= PGSS_V1_1)
@@ -2143,6 +2210,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
21432210
api_version == PGSS_V1_12 ? PG_STAT_STATEMENTS_COLS_V1_12 :
21442211
api_version == PGSS_V1_13 ? PG_STAT_STATEMENTS_COLS_V1_13 :
21452212
api_version == PGSS_V1_14 ? PG_STAT_STATEMENTS_COLS_V1_14 :
2213+
api_version == PGSS_V1_15 ? PG_STAT_STATEMENTS_COLS_V1_15 :
21462214
-1 /* fail if you forget to update this assert */ ));
21472215

21482216
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pg_stat_statements extension
22
comment = 'track planning and execution statistics of all SQL statements executed'
3-
default_version = '1.14'
3+
default_version = '1.15'
44
module_pathname = '$libdir/pg_stat_statements'
55
relocatable = true

0 commit comments

Comments
 (0)