Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pg_stat_statements.c
4 : : * Track statement planning and execution times as well as resource
5 : : * usage across a whole database cluster.
6 : : *
7 : : * Execution costs are totaled for each distinct source query, and kept in
8 : : * a shared hashtable. (We track only as many distinct queries as will fit
9 : : * in the designated amount of shared memory.)
10 : : *
11 : : * Starting in Postgres 9.2, this module normalized query entries. As of
12 : : * Postgres 14, the normalization is done by the core if compute_query_id is
13 : : * enabled, or optionally by third-party modules.
14 : : *
15 : : * To facilitate presenting entries to users, we create "representative" query
16 : : * strings in which constants are replaced with parameter symbols ($n), to
17 : : * make it clearer what a normalized entry can represent. To save on shared
18 : : * memory, and to avoid having to truncate oversized query strings, we store
19 : : * these strings in a temporary external query-texts file. Offsets into this
20 : : * file are kept in shared memory.
21 : : *
22 : : * Note about locking issues: to create or delete an entry in the shared
23 : : * hashtable, one must hold pgss->lock exclusively. Modifying any field
24 : : * in an entry except the counters requires the same. To look up an entry,
25 : : * one must hold the lock shared. To read or update the counters within
26 : : * an entry, one must hold the lock shared or exclusive (so the entry doesn't
27 : : * disappear!) and also take the entry's mutex spinlock.
28 : : * The shared state variable pgss->extent (the next free spot in the external
29 : : * query-text file) should be accessed only while holding either the
30 : : * pgss->mutex spinlock, or exclusive lock on pgss->lock. We use the mutex to
31 : : * allow reserving file space while holding only shared lock on pgss->lock.
32 : : * Rewriting the entire external query-text file, eg for garbage collection,
33 : : * requires holding pgss->lock exclusively; this allows individual entries
34 : : * in the file to be read or written while holding only shared lock.
35 : : *
36 : : *
37 : : * Copyright (c) 2008-2026, PostgreSQL Global Development Group
38 : : *
39 : : * IDENTIFICATION
40 : : * contrib/pg_stat_statements/pg_stat_statements.c
41 : : *
42 : : *-------------------------------------------------------------------------
43 : : */
44 : : #include "postgres.h"
45 : :
46 : : #include <math.h>
47 : : #include <sys/stat.h>
48 : : #include <unistd.h>
49 : :
50 : : #include "access/htup_details.h"
51 : : #include "access/parallel.h"
52 : : #include "catalog/pg_authid.h"
53 : : #include "executor/instrument.h"
54 : : #include "funcapi.h"
55 : : #include "jit/jit.h"
56 : : #include "mb/pg_wchar.h"
57 : : #include "miscadmin.h"
58 : : #include "nodes/queryjumble.h"
59 : : #include "optimizer/planner.h"
60 : : #include "parser/analyze.h"
61 : : #include "pgstat.h"
62 : : #include "storage/fd.h"
63 : : #include "storage/ipc.h"
64 : : #include "storage/lwlock.h"
65 : : #include "storage/shmem.h"
66 : : #include "storage/spin.h"
67 : : #include "tcop/utility.h"
68 : : #include "utils/acl.h"
69 : : #include "utils/builtins.h"
70 : : #include "utils/memutils.h"
71 : : #include "utils/timestamp.h"
72 : : #include "utils/tuplestore.h"
73 : :
74 : 10 : PG_MODULE_MAGIC_EXT(
75 : : .name = "pg_stat_statements",
76 : : .version = PG_VERSION
77 : : );
78 : :
79 : : /* Location of permanent stats file (valid when database is shut down) */
80 : : #define PGSS_DUMP_FILE PGSTAT_STAT_PERMANENT_DIRECTORY "/pg_stat_statements.stat"
81 : :
82 : : /*
83 : : * Location of external query text file.
84 : : */
85 : : #define PGSS_TEXT_FILE PG_STAT_TMP_DIR "/pgss_query_texts.stat"
86 : :
87 : : /* Magic number identifying the stats file format */
88 : : static const uint32 PGSS_FILE_HEADER = 0x20250731;
89 : :
90 : : /* PostgreSQL major version number, changes in which invalidate all entries */
91 : : static const uint32 PGSS_PG_MAJOR_VERSION = PG_VERSION_NUM / 100;
92 : :
93 : : /* XXX: Should USAGE_EXEC reflect execution time and/or buffer usage? */
94 : : #define USAGE_EXEC(duration) (1.0)
95 : : #define USAGE_INIT (1.0) /* including initial planning */
96 : : #define ASSUMED_MEDIAN_INIT (10.0) /* initial assumed median usage */
97 : : #define ASSUMED_LENGTH_INIT 1024 /* initial assumed mean query length */
98 : : #define USAGE_DECREASE_FACTOR (0.99) /* decreased every entry_dealloc */
99 : : #define STICKY_DECREASE_FACTOR (0.50) /* factor for sticky entries */
100 : : #define USAGE_DEALLOC_PERCENT 5 /* free this % of entries at once */
101 : : #define IS_STICKY(c) ((c.calls[PGSS_PLAN] + c.calls[PGSS_EXEC]) == 0)
102 : :
103 : : /*
104 : : * Extension version number, for supporting older extension versions' objects
105 : : */
106 : : typedef enum pgssVersion
107 : : {
108 : : PGSS_V1_0 = 0,
109 : : PGSS_V1_1,
110 : : PGSS_V1_2,
111 : : PGSS_V1_3,
112 : : PGSS_V1_8,
113 : : PGSS_V1_9,
114 : : PGSS_V1_10,
115 : : PGSS_V1_11,
116 : : PGSS_V1_12,
117 : : PGSS_V1_13,
118 : : } pgssVersion;
119 : :
120 : : typedef enum pgssStoreKind
121 : : {
122 : : PGSS_INVALID = -1,
123 : :
124 : : /*
125 : : * PGSS_PLAN and PGSS_EXEC must be respectively 0 and 1 as they're used to
126 : : * reference the underlying values in the arrays in the Counters struct,
127 : : * and this order is required in pg_stat_statements_internal().
128 : : */
129 : : PGSS_PLAN = 0,
130 : : PGSS_EXEC,
131 : : } pgssStoreKind;
132 : :
133 : : #define PGSS_NUMKIND (PGSS_EXEC + 1)
134 : :
135 : : /*
136 : : * Hashtable key that defines the identity of a hashtable entry. We separate
137 : : * queries by user and by database even if they are otherwise identical.
138 : : *
139 : : * If you add a new key to this struct, make sure to teach pgss_store() to
140 : : * zero the padding bytes. Otherwise, things will break, because pgss_hash is
141 : : * created using HASH_BLOBS, and thus tag_hash is used to hash this.
142 : : */
143 : : typedef struct pgssHashKey
144 : : {
145 : : Oid userid; /* user OID */
146 : : Oid dbid; /* database OID */
147 : : int64 queryid; /* query identifier */
148 : : bool toplevel; /* query executed at top level */
149 : : } pgssHashKey;
150 : :
151 : : /*
152 : : * The actual stats counters kept within pgssEntry.
153 : : */
154 : : typedef struct Counters
155 : : {
156 : : int64 calls[PGSS_NUMKIND]; /* # of times planned/executed */
157 : : double total_time[PGSS_NUMKIND]; /* total planning/execution time,
158 : : * in msec */
159 : : double min_time[PGSS_NUMKIND]; /* minimum planning/execution time in
160 : : * msec since min/max reset */
161 : : double max_time[PGSS_NUMKIND]; /* maximum planning/execution time in
162 : : * msec since min/max reset */
163 : : double mean_time[PGSS_NUMKIND]; /* mean planning/execution time in
164 : : * msec */
165 : : double sum_var_time[PGSS_NUMKIND]; /* sum of variances in
166 : : * planning/execution time in msec */
167 : : int64 rows; /* total # of retrieved or affected rows */
168 : : int64 shared_blks_hit; /* # of shared buffer hits */
169 : : int64 shared_blks_read; /* # of shared disk blocks read */
170 : : int64 shared_blks_dirtied; /* # of shared disk blocks dirtied */
171 : : int64 shared_blks_written; /* # of shared disk blocks written */
172 : : int64 local_blks_hit; /* # of local buffer hits */
173 : : int64 local_blks_read; /* # of local disk blocks read */
174 : : int64 local_blks_dirtied; /* # of local disk blocks dirtied */
175 : : int64 local_blks_written; /* # of local disk blocks written */
176 : : int64 temp_blks_read; /* # of temp blocks read */
177 : : int64 temp_blks_written; /* # of temp blocks written */
178 : : double shared_blk_read_time; /* time spent reading shared blocks,
179 : : * in msec */
180 : : double shared_blk_write_time; /* time spent writing shared blocks,
181 : : * in msec */
182 : : double local_blk_read_time; /* time spent reading local blocks, in
183 : : * msec */
184 : : double local_blk_write_time; /* time spent writing local blocks, in
185 : : * msec */
186 : : double temp_blk_read_time; /* time spent reading temp blocks, in msec */
187 : : double temp_blk_write_time; /* time spent writing temp blocks, in
188 : : * msec */
189 : : double usage; /* usage factor */
190 : : int64 wal_records; /* # of WAL records generated */
191 : : int64 wal_fpi; /* # of WAL full page images generated */
192 : : uint64 wal_bytes; /* total amount of WAL generated in bytes */
193 : : int64 wal_buffers_full; /* # of times the WAL buffers became full */
194 : : int64 jit_functions; /* total number of JIT functions emitted */
195 : : double jit_generation_time; /* total time to generate jit code */
196 : : int64 jit_inlining_count; /* number of times inlining time has been
197 : : * > 0 */
198 : : double jit_deform_time; /* total time to deform tuples in jit code */
199 : : int64 jit_deform_count; /* number of times deform time has been >
200 : : * 0 */
201 : :
202 : : double jit_inlining_time; /* total time to inline jit code */
203 : : int64 jit_optimization_count; /* number of times optimization time
204 : : * has been > 0 */
205 : : double jit_optimization_time; /* total time to optimize jit code */
206 : : int64 jit_emission_count; /* number of times emission time has been
207 : : * > 0 */
208 : : double jit_emission_time; /* total time to emit jit code */
209 : : int64 parallel_workers_to_launch; /* # of parallel workers planned
210 : : * to be launched */
211 : : int64 parallel_workers_launched; /* # of parallel workers actually
212 : : * launched */
213 : : int64 generic_plan_calls; /* number of calls using a generic plan */
214 : : int64 custom_plan_calls; /* number of calls using a custom plan */
215 : : } Counters;
216 : :
217 : : /*
218 : : * Global statistics for pg_stat_statements
219 : : */
220 : : typedef struct pgssGlobalStats
221 : : {
222 : : int64 dealloc; /* # of times entries were deallocated */
223 : : TimestampTz stats_reset; /* timestamp with all stats reset */
224 : : } pgssGlobalStats;
225 : :
226 : : /*
227 : : * Statistics per statement
228 : : *
229 : : * Note: in event of a failure in garbage collection of the query text file,
230 : : * we reset query_offset to zero and query_len to -1. This will be seen as
231 : : * an invalid state by qtext_fetch().
232 : : */
233 : : typedef struct pgssEntry
234 : : {
235 : : pgssHashKey key; /* hash key of entry - MUST BE FIRST */
236 : : Counters counters; /* the statistics for this query */
237 : : Size query_offset; /* query text offset in external file */
238 : : int query_len; /* # of valid bytes in query string, or -1 */
239 : : int encoding; /* query text encoding */
240 : : TimestampTz stats_since; /* timestamp of entry allocation */
241 : : TimestampTz minmax_stats_since; /* timestamp of last min/max values reset */
242 : : slock_t mutex; /* protects the counters only */
243 : : } pgssEntry;
244 : :
245 : : /*
246 : : * Global shared state
247 : : */
248 : : typedef struct pgssSharedState
249 : : {
250 : : LWLockPadded lock; /* protects hashtable search/modification */
251 : : double cur_median_usage; /* current median usage in hashtable */
252 : : Size mean_query_len; /* current mean entry text length */
253 : : slock_t mutex; /* protects following fields only: */
254 : : Size extent; /* current extent of query file */
255 : : int n_writers; /* number of active writers to query file */
256 : : int gc_count; /* query file garbage collection cycle count */
257 : : pgssGlobalStats stats; /* global statistics for pgss */
258 : : } pgssSharedState;
259 : :
260 : : /* Links to shared memory state */
261 : : static pgssSharedState *pgss;
262 : : static HTAB *pgss_hash;
263 : :
264 : : static void pgss_shmem_request(void *arg);
265 : : static void pgss_shmem_init(void *arg);
266 : :
267 : : static const ShmemCallbacks pgss_shmem_callbacks = {
268 : : .request_fn = pgss_shmem_request,
269 : : .init_fn = pgss_shmem_init,
270 : : };
271 : :
272 : : /*---- Local variables ----*/
273 : :
274 : : /* Current nesting depth of planner/ExecutorRun/ProcessUtility calls */
275 : : static int nesting_level = 0;
276 : :
277 : : /*
278 : : * State saved in a QueryDesc at ExecutorStart, for use at ExecutorEnd. A
279 : : * query may end at a different nesting level than the one it started at,
280 : : * for example a cursor declared in a transaction and closed at COMMIT.
281 : : */
282 : : typedef struct pgssQueryState
283 : : {
284 : : int nesting_level; /* nesting level at ExecutorStart */
285 : : } pgssQueryState;
286 : :
287 : : /* ID of pg_stat_statements for QueryDesc extension state */
288 : : static int pgss_queryDesc_id = -1;
289 : :
290 : : /* Saved hook values */
291 : : static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL;
292 : : static planner_hook_type prev_planner_hook = NULL;
293 : : static ExecutorStart_hook_type prev_ExecutorStart = NULL;
294 : : static ExecutorRun_hook_type prev_ExecutorRun = NULL;
295 : : static ExecutorFinish_hook_type prev_ExecutorFinish = NULL;
296 : : static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
297 : : static ProcessUtility_hook_type prev_ProcessUtility = NULL;
298 : :
299 : : /*---- GUC variables ----*/
300 : :
301 : : typedef enum
302 : : {
303 : : PGSS_TRACK_NONE, /* track no statements */
304 : : PGSS_TRACK_TOP, /* only top level statements */
305 : : PGSS_TRACK_ALL, /* all statements, including nested ones */
306 : : } PGSSTrackLevel;
307 : :
308 : : static const struct config_enum_entry track_options[] =
309 : : {
310 : : {"none", PGSS_TRACK_NONE, false},
311 : : {"top", PGSS_TRACK_TOP, false},
312 : : {"all", PGSS_TRACK_ALL, false},
313 : : {NULL, 0, false}
314 : : };
315 : :
316 : : static int pgss_max = 5000; /* max # statements to track */
317 : : static int pgss_track = PGSS_TRACK_TOP; /* tracking level */
318 : : static bool pgss_track_utility = true; /* whether to track utility commands */
319 : : static bool pgss_track_planning = false; /* whether to track planning
320 : : * duration */
321 : : static bool pgss_save = true; /* whether to save stats across shutdown */
322 : :
323 : : #define pgss_enabled(level) \
324 : : (!IsParallelWorker() && \
325 : : (pgss_track == PGSS_TRACK_ALL || \
326 : : (pgss_track == PGSS_TRACK_TOP && (level) == 0)))
327 : :
328 : : #define record_gc_qtexts() \
329 : : do { \
330 : : SpinLockAcquire(&pgss->mutex); \
331 : : pgss->gc_count++; \
332 : : SpinLockRelease(&pgss->mutex); \
333 : : } while(0)
334 : :
335 : : /*---- Function declarations ----*/
336 : :
337 : 6 : PG_FUNCTION_INFO_V1(pg_stat_statements_reset);
338 : 6 : PG_FUNCTION_INFO_V1(pg_stat_statements_reset_1_7);
339 : 21 : PG_FUNCTION_INFO_V1(pg_stat_statements_reset_1_11);
340 : 0 : PG_FUNCTION_INFO_V1(pg_stat_statements_1_2);
341 : 6 : PG_FUNCTION_INFO_V1(pg_stat_statements_1_3);
342 : 6 : PG_FUNCTION_INFO_V1(pg_stat_statements_1_8);
343 : 6 : PG_FUNCTION_INFO_V1(pg_stat_statements_1_9);
344 : 6 : PG_FUNCTION_INFO_V1(pg_stat_statements_1_10);
345 : 6 : PG_FUNCTION_INFO_V1(pg_stat_statements_1_11);
346 : 6 : PG_FUNCTION_INFO_V1(pg_stat_statements_1_12);
347 : 25 : PG_FUNCTION_INFO_V1(pg_stat_statements_1_13);
348 : 0 : PG_FUNCTION_INFO_V1(pg_stat_statements);
349 : 8 : PG_FUNCTION_INFO_V1(pg_stat_statements_info);
350 : :
351 : : static void pgss_shmem_shutdown(int code, Datum arg);
352 : : static void pgss_post_parse_analyze(ParseState *pstate, Query *query,
353 : : const JumbleState *jstate);
354 : : static PlannedStmt *pgss_planner(Query *parse,
355 : : const char *query_string,
356 : : int cursorOptions,
357 : : ParamListInfo boundParams,
358 : : ExplainState *es);
359 : : static void pgss_ExecutorStart(QueryDesc *queryDesc, int eflags);
360 : : static void pgss_ExecutorRun(QueryDesc *queryDesc,
361 : : ScanDirection direction,
362 : : uint64 count);
363 : : static void pgss_ExecutorFinish(QueryDesc *queryDesc);
364 : : static void pgss_ExecutorEnd(QueryDesc *queryDesc);
365 : : static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
366 : : bool readOnlyTree,
367 : : ProcessUtilityContext context, ParamListInfo params,
368 : : QueryEnvironment *queryEnv,
369 : : DestReceiver *dest, QueryCompletion *qc);
370 : : static void pgss_store(const char *query, int64 queryId,
371 : : int query_location, int query_len,
372 : : bool toplevel,
373 : : pgssStoreKind kind,
374 : : double total_time, uint64 rows,
375 : : const BufferUsage *bufusage,
376 : : const WalUsage *walusage,
377 : : const struct JitInstrumentation *jitusage,
378 : : const JumbleState *jstate,
379 : : int parallel_workers_to_launch,
380 : : int parallel_workers_launched,
381 : : PlannedStmtOrigin planOrigin);
382 : : static void pg_stat_statements_internal(FunctionCallInfo fcinfo,
383 : : pgssVersion api_version,
384 : : bool showtext);
385 : : static pgssEntry *entry_alloc(pgssHashKey *key, Size query_offset, int query_len,
386 : : int encoding, bool sticky);
387 : : static void entry_dealloc(void);
388 : : static bool qtext_store(const char *query, int query_len,
389 : : Size *query_offset, int *gc_count);
390 : : static char *qtext_load_file(Size *buffer_size);
391 : : static char *qtext_fetch(Size query_offset, int query_len,
392 : : char *buffer, Size buffer_size);
393 : : static bool need_gc_qtexts(void);
394 : : static void gc_qtexts(void);
395 : : static TimestampTz entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only);
396 : : static char *generate_normalized_query(const JumbleState *jstate,
397 : : const char *query,
398 : : int query_loc, int *query_len_p);
399 : :
400 : : /*
401 : : * Module load callback
402 : : */
403 : : void
404 : 10 : _PG_init(void)
405 : : {
406 : : /*
407 : : * In order to create our shared memory area, we have to be loaded via
408 : : * shared_preload_libraries. If not, fall out without hooking into any of
409 : : * the main system. (We don't throw error here because it seems useful to
410 : : * allow the pg_stat_statements functions to be created even when the
411 : : * module isn't active. The functions must protect themselves against
412 : : * being called then, however.)
413 : : */
414 [ + + ]: 10 : if (!process_shared_preload_libraries_in_progress)
415 : 1 : return;
416 : :
417 : : /*
418 : : * Inform the postmaster that we want to enable query_id calculation if
419 : : * compute_query_id is set to auto.
420 : : */
421 : 9 : EnableQueryId();
422 : :
423 : : /*
424 : : * Define (or redefine) custom GUC variables.
425 : : */
426 : 9 : DefineCustomIntVariable("pg_stat_statements.max",
427 : : "Sets the maximum number of statements tracked by pg_stat_statements.",
428 : : NULL,
429 : : &pgss_max,
430 : : 5000,
431 : : 100,
432 : : INT_MAX / 2,
433 : : PGC_POSTMASTER,
434 : : 0,
435 : : NULL,
436 : : NULL,
437 : : NULL);
438 : :
439 : 9 : DefineCustomEnumVariable("pg_stat_statements.track",
440 : : "Selects which statements are tracked by pg_stat_statements.",
441 : : NULL,
442 : : &pgss_track,
443 : : PGSS_TRACK_TOP,
444 : : track_options,
445 : : PGC_SUSET,
446 : : 0,
447 : : NULL,
448 : : NULL,
449 : : NULL);
450 : :
451 : 9 : DefineCustomBoolVariable("pg_stat_statements.track_utility",
452 : : "Selects whether utility commands are tracked by pg_stat_statements.",
453 : : NULL,
454 : : &pgss_track_utility,
455 : : true,
456 : : PGC_SUSET,
457 : : 0,
458 : : NULL,
459 : : NULL,
460 : : NULL);
461 : :
462 : 9 : DefineCustomBoolVariable("pg_stat_statements.track_planning",
463 : : "Selects whether planning duration is tracked by pg_stat_statements.",
464 : : NULL,
465 : : &pgss_track_planning,
466 : : false,
467 : : PGC_SUSET,
468 : : 0,
469 : : NULL,
470 : : NULL,
471 : : NULL);
472 : :
473 : 9 : DefineCustomBoolVariable("pg_stat_statements.save",
474 : : "Save pg_stat_statements statistics across server shutdowns.",
475 : : NULL,
476 : : &pgss_save,
477 : : true,
478 : : PGC_SIGHUP,
479 : : 0,
480 : : NULL,
481 : : NULL,
482 : : NULL);
483 : :
484 : 9 : MarkGUCPrefixReserved("pg_stat_statements");
485 : :
486 : : /*
487 : : * Register our shared memory needs.
488 : : */
489 : 9 : RegisterShmemCallbacks(&pgss_shmem_callbacks);
490 : :
491 : : /*
492 : : * Get an ID to store state in QueryDesc.
493 : : */
494 : 9 : pgss_queryDesc_id = GetQueryDescExtensionId("pg_stat_statements");
495 : :
496 : : /*
497 : : * Install hooks.
498 : : */
499 : 9 : prev_post_parse_analyze_hook = post_parse_analyze_hook;
500 : 9 : post_parse_analyze_hook = pgss_post_parse_analyze;
501 : 9 : prev_planner_hook = planner_hook;
502 : 9 : planner_hook = pgss_planner;
503 : 9 : prev_ExecutorStart = ExecutorStart_hook;
504 : 9 : ExecutorStart_hook = pgss_ExecutorStart;
505 : 9 : prev_ExecutorRun = ExecutorRun_hook;
506 : 9 : ExecutorRun_hook = pgss_ExecutorRun;
507 : 9 : prev_ExecutorFinish = ExecutorFinish_hook;
508 : 9 : ExecutorFinish_hook = pgss_ExecutorFinish;
509 : 9 : prev_ExecutorEnd = ExecutorEnd_hook;
510 : 9 : ExecutorEnd_hook = pgss_ExecutorEnd;
511 : 9 : prev_ProcessUtility = ProcessUtility_hook;
512 : 9 : ProcessUtility_hook = pgss_ProcessUtility;
513 : : }
514 : :
515 : : /*
516 : : * shmem request callback: Request shared memory resources.
517 : : *
518 : : * This is called at postmaster startup. Note that the shared memory isn't
519 : : * allocated here yet, this merely register our needs.
520 : : *
521 : : * In EXEC_BACKEND mode, this is also called in each backend, to re-attach to
522 : : * the shared memory area that was already initialized.
523 : : */
524 : : static void
525 : 11 : pgss_shmem_request(void *arg)
526 : : {
527 : 11 : ShmemRequestHash(.name = "pg_stat_statements hash",
528 : : .nelems = (int64) pgss_max,
529 : : .hash_info.keysize = sizeof(pgssHashKey),
530 : : .hash_info.entrysize = sizeof(pgssEntry),
531 : : .hash_flags = HASH_ELEM | HASH_BLOBS,
532 : : .ptr = &pgss_hash,
533 : : );
534 : 11 : ShmemRequestStruct(.name = "pg_stat_statements",
535 : : .size = sizeof(pgssSharedState),
536 : : .ptr = (void **) &pgss,
537 : : );
538 : 11 : }
539 : :
540 : : /*
541 : : * shmem init callback: Initialize our shared memory data structures at
542 : : * postmaster startup.
543 : : *
544 : : * Load any pre-existing statistics from file. Also create and load the
545 : : * query-texts file, which is expected to exist (even if empty) while the
546 : : * module is enabled.
547 : : */
548 : : static void
549 : 11 : pgss_shmem_init(void *arg)
550 : : {
551 : : int tranche_id;
552 : 11 : FILE *file = NULL;
553 : 11 : FILE *qfile = NULL;
554 : : uint32 header;
555 : : int64 num;
556 : : uint32 pgver;
557 : : int buffer_size;
558 : 11 : char *buffer = NULL;
559 : :
560 : : /*
561 : : * We already checked that we're loaded from shared_preload_libraries in
562 : : * _PG_init(), so we should not get here after postmaster startup.
563 : : */
564 : : Assert(!IsUnderPostmaster);
565 : :
566 : : /*
567 : : * Initialize the shmem area with no statistics.
568 : : */
569 : 11 : tranche_id = LWLockNewTrancheId("pg_stat_statements");
570 : 11 : LWLockInitialize(&pgss->lock.lock, tranche_id);
571 : 11 : pgss->cur_median_usage = ASSUMED_MEDIAN_INIT;
572 : 11 : pgss->mean_query_len = ASSUMED_LENGTH_INIT;
573 : 11 : SpinLockInit(&pgss->mutex);
574 : 11 : pgss->extent = 0;
575 : 11 : pgss->n_writers = 0;
576 : 11 : pgss->gc_count = 0;
577 : 11 : pgss->stats.dealloc = 0;
578 : 11 : pgss->stats.stats_reset = GetCurrentTimestamp();
579 : :
580 : : /* The hash table must've also been initialized by now */
581 : : Assert(pgss_hash != NULL);
582 : :
583 : : /*
584 : : * Set up a shmem exit hook to dump the statistics to disk on postmaster
585 : : * (or standalone backend) exit.
586 : : */
587 : 11 : on_shmem_exit(pgss_shmem_shutdown, (Datum) 0);
588 : :
589 : : /*
590 : : * Load any pre-existing statistics from file.
591 : : *
592 : : * Note: we don't bother with locks here, because there should be no other
593 : : * processes running when this code is reached.
594 : : */
595 : :
596 : : /* Unlink query text file possibly left over from crash */
597 : 11 : unlink(PGSS_TEXT_FILE);
598 : :
599 : : /* Allocate new query text temp file */
600 : 11 : qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
601 [ - + ]: 11 : if (qfile == NULL)
602 : 0 : goto write_error;
603 : :
604 : : /*
605 : : * If we were told not to load old statistics, we're done. (Note we do
606 : : * not try to unlink any old dump file in this case. This seems a bit
607 : : * questionable but it's the historical behavior.)
608 : : */
609 [ + + ]: 11 : if (!pgss_save)
610 : : {
611 : 1 : FreeFile(qfile);
612 : 11 : return;
613 : : }
614 : :
615 : : /*
616 : : * Attempt to load old statistics from the dump file.
617 : : */
618 : 10 : file = AllocateFile(PGSS_DUMP_FILE, PG_BINARY_R);
619 [ + + ]: 10 : if (file == NULL)
620 : : {
621 [ - + ]: 7 : if (errno != ENOENT)
622 : 0 : goto read_error;
623 : : /* No existing persisted stats file, so we're done */
624 : 7 : FreeFile(qfile);
625 : 7 : return;
626 : : }
627 : :
628 : 3 : buffer_size = 2048;
629 : 3 : buffer = (char *) palloc(buffer_size);
630 : :
631 [ + - + - ]: 6 : if (fread(&header, sizeof(uint32), 1, file) != 1 ||
632 [ - + ]: 6 : fread(&pgver, sizeof(uint32), 1, file) != 1 ||
633 : 3 : fread(&num, sizeof(int64), 1, file) != 1)
634 : 0 : goto read_error;
635 : :
636 [ + - ]: 3 : if (header != PGSS_FILE_HEADER ||
637 [ - + ]: 3 : pgver != PGSS_PG_MAJOR_VERSION)
638 : 0 : goto data_error;
639 : :
640 [ + + ]: 29247 : for (int64 i = 0; i < num; i++)
641 : : {
642 : : pgssEntry temp;
643 : : pgssEntry *entry;
644 : : Size query_offset;
645 : :
646 [ - + ]: 29244 : if (fread(&temp, sizeof(pgssEntry), 1, file) != 1)
647 : 0 : goto read_error;
648 : :
649 : : /* Encoding is the only field we can easily sanity-check */
650 [ + - + - : 29244 : if (!PG_VALID_BE_ENCODING(temp.encoding))
- + ]
651 : 0 : goto data_error;
652 : :
653 : : /* Resize buffer as needed */
654 [ + + ]: 29244 : if (temp.query_len >= buffer_size)
655 : : {
656 : 4 : buffer_size = Max(buffer_size * 2, temp.query_len + 1);
657 : 4 : buffer = repalloc(buffer, buffer_size);
658 : : }
659 : :
660 [ - + ]: 29244 : if (fread(buffer, 1, temp.query_len + 1, file) != temp.query_len + 1)
661 : 0 : goto read_error;
662 : :
663 : : /* Should have a trailing null, but let's make sure */
664 : 29244 : buffer[temp.query_len] = '\0';
665 : :
666 : : /* Skip loading "sticky" entries */
667 [ + + ]: 29244 : if (IS_STICKY(temp.counters))
668 : 802 : continue;
669 : :
670 : : /* Store the query text */
671 : 28442 : query_offset = pgss->extent;
672 [ - + ]: 28442 : if (fwrite(buffer, 1, temp.query_len + 1, qfile) != temp.query_len + 1)
673 : 0 : goto write_error;
674 : 28442 : pgss->extent += temp.query_len + 1;
675 : :
676 : : /* make the hashtable entry (discards old entries if too many) */
677 : 28442 : entry = entry_alloc(&temp.key, query_offset, temp.query_len,
678 : : temp.encoding,
679 : : false);
680 : :
681 : : /* copy in the actual stats */
682 : 28442 : entry->counters = temp.counters;
683 : 28442 : entry->stats_since = temp.stats_since;
684 : 28442 : entry->minmax_stats_since = temp.minmax_stats_since;
685 : : }
686 : :
687 : : /* Read global statistics for pg_stat_statements */
688 [ - + ]: 3 : if (fread(&pgss->stats, sizeof(pgssGlobalStats), 1, file) != 1)
689 : 0 : goto read_error;
690 : :
691 : 3 : pfree(buffer);
692 : 3 : FreeFile(file);
693 : 3 : FreeFile(qfile);
694 : :
695 : : /*
696 : : * Remove the persisted stats file so it's not included in
697 : : * backups/replication standbys, etc. A new file will be written on next
698 : : * shutdown.
699 : : *
700 : : * Note: it's okay if the PGSS_TEXT_FILE is included in a basebackup,
701 : : * because we remove that file on startup; it acts inversely to
702 : : * PGSS_DUMP_FILE, in that it is only supposed to be around when the
703 : : * server is running, whereas PGSS_DUMP_FILE is only supposed to be around
704 : : * when the server is not running. Leaving the file creates no danger of
705 : : * a newly restored database having a spurious record of execution costs,
706 : : * which is what we're really concerned about here.
707 : : */
708 : 3 : unlink(PGSS_DUMP_FILE);
709 : :
710 : 3 : return;
711 : :
712 : 0 : read_error:
713 [ # # ]: 0 : ereport(LOG,
714 : : (errcode_for_file_access(),
715 : : errmsg("could not read file \"%s\": %m",
716 : : PGSS_DUMP_FILE)));
717 : 0 : goto fail;
718 : 0 : data_error:
719 [ # # ]: 0 : ereport(LOG,
720 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
721 : : errmsg("ignoring invalid data in file \"%s\"",
722 : : PGSS_DUMP_FILE)));
723 : 0 : goto fail;
724 : 0 : write_error:
725 [ # # ]: 0 : ereport(LOG,
726 : : (errcode_for_file_access(),
727 : : errmsg("could not write file \"%s\": %m",
728 : : PGSS_TEXT_FILE)));
729 : 0 : fail:
730 [ # # ]: 0 : if (buffer)
731 : 0 : pfree(buffer);
732 [ # # ]: 0 : if (file)
733 : 0 : FreeFile(file);
734 [ # # ]: 0 : if (qfile)
735 : 0 : FreeFile(qfile);
736 : : /* If possible, throw away the bogus file; ignore any error */
737 : 0 : unlink(PGSS_DUMP_FILE);
738 : :
739 : : /*
740 : : * Don't unlink PGSS_TEXT_FILE here; it should always be around while the
741 : : * server is running with pg_stat_statements enabled
742 : : */
743 : : }
744 : :
745 : : /*
746 : : * shmem_shutdown hook: Dump statistics into file.
747 : : *
748 : : * Note: we don't bother with acquiring lock, because there should be no
749 : : * other processes running when this is called.
750 : : */
751 : : static void
752 : 11 : pgss_shmem_shutdown(int code, Datum arg)
753 : : {
754 : : FILE *file;
755 : 11 : char *qbuffer = NULL;
756 : 11 : Size qbuffer_size = 0;
757 : : HASH_SEQ_STATUS hash_seq;
758 : : int64 num_entries;
759 : : pgssEntry *entry;
760 : :
761 : : /* Don't try to dump during a crash. */
762 [ + + ]: 11 : if (code)
763 : 11 : return;
764 : :
765 : : /* Safety check ... shouldn't get here unless shmem is set up. */
766 [ + - - + ]: 9 : if (!pgss || !pgss_hash)
767 : 0 : return;
768 : :
769 : : /* Don't dump if told not to. */
770 [ + + ]: 9 : if (!pgss_save)
771 : 2 : return;
772 : :
773 : 7 : file = AllocateFile(PGSS_DUMP_FILE ".tmp", PG_BINARY_W);
774 [ - + ]: 7 : if (file == NULL)
775 : 0 : goto error;
776 : :
777 [ - + ]: 7 : if (fwrite(&PGSS_FILE_HEADER, sizeof(uint32), 1, file) != 1)
778 : 0 : goto error;
779 [ - + ]: 7 : if (fwrite(&PGSS_PG_MAJOR_VERSION, sizeof(uint32), 1, file) != 1)
780 : 0 : goto error;
781 : 7 : num_entries = hash_get_num_entries(pgss_hash);
782 [ - + ]: 7 : if (fwrite(&num_entries, sizeof(int64), 1, file) != 1)
783 : 0 : goto error;
784 : :
785 : 7 : qbuffer = qtext_load_file(&qbuffer_size);
786 [ - + ]: 7 : if (qbuffer == NULL)
787 : 0 : goto error;
788 : :
789 : : /*
790 : : * When serializing to disk, we store query texts immediately after their
791 : : * entry data. Any orphaned query texts are thereby excluded.
792 : : */
793 : 7 : hash_seq_init(&hash_seq, pgss_hash);
794 [ + + ]: 58721 : while ((entry = hash_seq_search(&hash_seq)) != NULL)
795 : : {
796 : 58714 : int len = entry->query_len;
797 : 58714 : char *qstr = qtext_fetch(entry->query_offset, len,
798 : : qbuffer, qbuffer_size);
799 : :
800 [ - + ]: 58714 : if (qstr == NULL)
801 : 0 : continue; /* Ignore any entries with bogus texts */
802 : :
803 [ + - ]: 58714 : if (fwrite(entry, sizeof(pgssEntry), 1, file) != 1 ||
804 [ - + ]: 58714 : fwrite(qstr, 1, len + 1, file) != len + 1)
805 : : {
806 : : /* note: we assume hash_seq_term won't change errno */
807 : 0 : hash_seq_term(&hash_seq);
808 : 0 : goto error;
809 : : }
810 : : }
811 : :
812 : : /* Dump global statistics for pg_stat_statements */
813 [ - + ]: 7 : if (fwrite(&pgss->stats, sizeof(pgssGlobalStats), 1, file) != 1)
814 : 0 : goto error;
815 : :
816 : 7 : pfree(qbuffer);
817 : 7 : qbuffer = NULL;
818 : :
819 [ - + ]: 7 : if (FreeFile(file))
820 : : {
821 : 0 : file = NULL;
822 : 0 : goto error;
823 : : }
824 : :
825 : : /*
826 : : * Rename file into place, so we atomically replace any old one.
827 : : */
828 : 7 : (void) durable_rename(PGSS_DUMP_FILE ".tmp", PGSS_DUMP_FILE, LOG);
829 : :
830 : : /* Unlink query-texts file; it's not needed while shutdown */
831 : 7 : unlink(PGSS_TEXT_FILE);
832 : :
833 : 7 : return;
834 : :
835 : 0 : error:
836 [ # # ]: 0 : ereport(LOG,
837 : : (errcode_for_file_access(),
838 : : errmsg("could not write file \"%s\": %m",
839 : : PGSS_DUMP_FILE ".tmp")));
840 [ # # ]: 0 : if (qbuffer)
841 : 0 : pfree(qbuffer);
842 [ # # ]: 0 : if (file)
843 : 0 : FreeFile(file);
844 : 0 : unlink(PGSS_DUMP_FILE ".tmp");
845 : 0 : unlink(PGSS_TEXT_FILE);
846 : : }
847 : :
848 : : /*
849 : : * Post-parse-analysis hook: mark query with a queryId
850 : : */
851 : : static void
852 : 84309 : pgss_post_parse_analyze(ParseState *pstate, Query *query, const JumbleState *jstate)
853 : : {
854 [ - + ]: 84309 : if (prev_post_parse_analyze_hook)
855 : 0 : prev_post_parse_analyze_hook(pstate, query, jstate);
856 : :
857 : : /* Safety check... */
858 [ + - + - : 84309 : if (!pgss || !pgss_hash || !pgss_enabled(nesting_level))
+ + + + +
+ + + ]
859 : 13141 : return;
860 : :
861 : : /*
862 : : * If it's EXECUTE, clear the queryId so that stats will accumulate for
863 : : * the underlying PREPARE. But don't do this if we're not tracking
864 : : * utility statements, to avoid messing up another extension that might be
865 : : * tracking them.
866 : : */
867 [ + + ]: 71168 : if (query->utilityStmt)
868 : : {
869 [ + + + + ]: 31874 : if (pgss_track_utility && IsA(query->utilityStmt, ExecuteStmt))
870 : : {
871 : 3304 : query->queryId = INT64CONST(0);
872 : 3304 : return;
873 : : }
874 : : }
875 : :
876 : : /*
877 : : * If query jumbling were able to identify any ignorable constants, we
878 : : * immediately create a hash table entry for the query, so that we can
879 : : * record the normalized form of the query string. If there were no such
880 : : * constants, the normalized string would be the same as the query text
881 : : * anyway, so there's no need for an early entry.
882 : : */
883 [ + - + + ]: 67864 : if (jstate && jstate->clocations_count > 0)
884 : 39322 : pgss_store(pstate->p_sourcetext,
885 : : query->queryId,
886 : : query->stmt_location,
887 : : query->stmt_len,
888 : : nesting_level == 0,
889 : : PGSS_INVALID,
890 : : 0,
891 : : 0,
892 : : NULL,
893 : : NULL,
894 : : NULL,
895 : : jstate,
896 : : 0,
897 : : 0,
898 : : PLAN_STMT_UNKNOWN);
899 : : }
900 : :
901 : : /*
902 : : * Planner hook: forward to regular planner, but measure planning time
903 : : * if needed.
904 : : */
905 : : static PlannedStmt *
906 : 50714 : pgss_planner(Query *parse,
907 : : const char *query_string,
908 : : int cursorOptions,
909 : : ParamListInfo boundParams,
910 : : ExplainState *es)
911 : : {
912 : : PlannedStmt *result;
913 : :
914 : : /*
915 : : * We can't process the query if no query_string is provided, as
916 : : * pgss_store needs it. We also ignore query without queryid, as it would
917 : : * be treated as a utility statement, which may not be the case.
918 : : */
919 [ + + + + : 50714 : if (pgss_enabled(nesting_level)
+ + + + ]
920 [ + + + - ]: 39455 : && pgss_track_planning && query_string
921 [ + - ]: 150 : && parse->queryId != INT64CONST(0))
922 : 150 : {
923 : : instr_time start;
924 : : instr_time duration;
925 : : BufferUsage bufusage_start,
926 : : bufusage;
927 : : WalUsage walusage_start,
928 : : walusage;
929 : :
930 : : /* We need to track buffer usage as the planner can access them. */
931 : 150 : bufusage_start = pgBufferUsage;
932 : :
933 : : /*
934 : : * Similarly the planner could write some WAL records in some cases
935 : : * (e.g. setting a hint bit with those being WAL-logged)
936 : : */
937 : 150 : walusage_start = pgWalUsage;
938 : 150 : INSTR_TIME_SET_CURRENT(start);
939 : :
940 : 150 : nesting_level++;
941 [ + - ]: 150 : PG_TRY();
942 : : {
943 [ - + ]: 150 : if (prev_planner_hook)
944 : 0 : result = prev_planner_hook(parse, query_string, cursorOptions,
945 : : boundParams, es);
946 : : else
947 : 150 : result = standard_planner(parse, query_string, cursorOptions,
948 : : boundParams, es);
949 : : }
950 : 0 : PG_FINALLY();
951 : : {
952 : 150 : nesting_level--;
953 : : }
954 [ - + ]: 150 : PG_END_TRY();
955 : :
956 : 150 : INSTR_TIME_SET_CURRENT(duration);
957 : 150 : INSTR_TIME_SUBTRACT(duration, start);
958 : :
959 : : /* calc differences of buffer counters. */
960 : 150 : memset(&bufusage, 0, sizeof(BufferUsage));
961 : 150 : BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start);
962 : :
963 : : /* calc differences of WAL counters. */
964 : 150 : memset(&walusage, 0, sizeof(WalUsage));
965 : 150 : WalUsageAccumDiff(&walusage, &pgWalUsage, &walusage_start);
966 : :
967 : 300 : pgss_store(query_string,
968 : : parse->queryId,
969 : : parse->stmt_location,
970 : : parse->stmt_len,
971 : : nesting_level == 0,
972 : : PGSS_PLAN,
973 : 150 : INSTR_TIME_GET_MILLISEC(duration),
974 : : 0,
975 : : &bufusage,
976 : : &walusage,
977 : : NULL,
978 : : NULL,
979 : : 0,
980 : : 0,
981 : : result->planOrigin);
982 : : }
983 : : else
984 : : {
985 : : /*
986 : : * Even though we're not tracking plan time for this statement, we
987 : : * must still increment the nesting level, to ensure that functions
988 : : * evaluated during planning are not seen as top-level calls.
989 : : */
990 : 50564 : nesting_level++;
991 [ + + ]: 50564 : PG_TRY();
992 : : {
993 [ - + ]: 50564 : if (prev_planner_hook)
994 : 0 : result = prev_planner_hook(parse, query_string, cursorOptions,
995 : : boundParams, es);
996 : : else
997 : 50564 : result = standard_planner(parse, query_string, cursorOptions,
998 : : boundParams, es);
999 : : }
1000 : 804 : PG_FINALLY();
1001 : : {
1002 : 50564 : nesting_level--;
1003 : : }
1004 [ + + ]: 50564 : PG_END_TRY();
1005 : : }
1006 : :
1007 : 49910 : return result;
1008 : : }
1009 : :
1010 : : /*
1011 : : * ExecutorStart hook: start up tracking if needed
1012 : : */
1013 : : static void
1014 : 60846 : pgss_ExecutorStart(QueryDesc *queryDesc, int eflags)
1015 : : {
1016 : : pgssQueryState *qstate;
1017 : :
1018 : : /*
1019 : : * If query has queryId zero, don't track it. This prevents double
1020 : : * counting of optimizable statements that are directly contained in
1021 : : * utility statements.
1022 : : */
1023 [ + + + + : 60846 : if (pgss_enabled(nesting_level) && queryDesc->plannedstmt->queryId != INT64CONST(0))
+ + + + +
+ ]
1024 : : {
1025 : : /* Request all summary instrumentation, i.e. timing, buffers and WAL */
1026 : 41632 : queryDesc->query_instr_options |= INSTRUMENT_ALL;
1027 : :
1028 : : /* Remember the nesting level for ExecutorEnd */
1029 : 41632 : qstate = MemoryContextAlloc(GetMemoryChunkContext(queryDesc),
1030 : : sizeof(pgssQueryState));
1031 : 41632 : qstate->nesting_level = nesting_level;
1032 : 41632 : SetQueryDescExtensionState(queryDesc, pgss_queryDesc_id, qstate);
1033 : : }
1034 : :
1035 [ - + ]: 60846 : if (prev_ExecutorStart)
1036 : 0 : prev_ExecutorStart(queryDesc, eflags);
1037 : : else
1038 : 60846 : standard_ExecutorStart(queryDesc, eflags);
1039 : 60572 : }
1040 : :
1041 : : /*
1042 : : * ExecutorRun hook: all we need do is track nesting depth
1043 : : */
1044 : : static void
1045 : 58930 : pgss_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count)
1046 : : {
1047 : 58930 : nesting_level++;
1048 [ + + ]: 58930 : PG_TRY();
1049 : : {
1050 [ - + ]: 58930 : if (prev_ExecutorRun)
1051 : 0 : prev_ExecutorRun(queryDesc, direction, count);
1052 : : else
1053 : 58930 : standard_ExecutorRun(queryDesc, direction, count);
1054 : : }
1055 : 3461 : PG_FINALLY();
1056 : : {
1057 : 58930 : nesting_level--;
1058 : : }
1059 [ + + ]: 58930 : PG_END_TRY();
1060 : 55469 : }
1061 : :
1062 : : /*
1063 : : * ExecutorFinish hook: all we need do is track nesting depth
1064 : : */
1065 : : static void
1066 : 53484 : pgss_ExecutorFinish(QueryDesc *queryDesc)
1067 : : {
1068 : 53484 : nesting_level++;
1069 [ + + ]: 53484 : PG_TRY();
1070 : : {
1071 [ - + ]: 53484 : if (prev_ExecutorFinish)
1072 : 0 : prev_ExecutorFinish(queryDesc);
1073 : : else
1074 : 53484 : standard_ExecutorFinish(queryDesc);
1075 : : }
1076 : 219 : PG_FINALLY();
1077 : : {
1078 : 53484 : nesting_level--;
1079 : : }
1080 [ + + ]: 53484 : PG_END_TRY();
1081 : 53265 : }
1082 : :
1083 : : /*
1084 : : * ExecutorEnd hook: store results if needed
1085 : : */
1086 : : static void
1087 : 56568 : pgss_ExecutorEnd(QueryDesc *queryDesc)
1088 : : {
1089 : 56568 : int64 queryId = queryDesc->plannedstmt->queryId;
1090 : 56568 : pgssQueryState *qstate = GetQueryDescExtensionState(queryDesc,
1091 : : pgss_queryDesc_id);
1092 : :
1093 : : /*
1094 : : * Use the nesting level saved at ExecutorStart, if available, since the
1095 : : * query may end at a different level (e.g. a cursor closed at COMMIT).
1096 : : */
1097 [ + + + + : 56568 : if (queryId != INT64CONST(0) && queryDesc->query_instr &&
+ - ]
1098 : 39666 : qstate != NULL &&
1099 [ + - + + : 39666 : pgss_enabled(qstate->nesting_level))
+ - + - ]
1100 : : {
1101 : 39666 : pgss_store(queryDesc->sourceText,
1102 : : queryId,
1103 : 39666 : queryDesc->plannedstmt->stmt_location,
1104 : 39666 : queryDesc->plannedstmt->stmt_len,
1105 : 39666 : qstate->nesting_level == 0,
1106 : : PGSS_EXEC,
1107 : 39666 : INSTR_TIME_GET_MILLISEC(queryDesc->query_instr->total),
1108 : 39666 : queryDesc->estate->es_total_processed,
1109 : 39666 : &queryDesc->query_instr->bufusage,
1110 : 39666 : &queryDesc->query_instr->walusage,
1111 : 0 : queryDesc->estate->es_jit ? &queryDesc->estate->es_jit->instr : NULL,
1112 : : NULL,
1113 : 39666 : queryDesc->estate->es_parallel_workers_to_launch,
1114 : 39666 : queryDesc->estate->es_parallel_workers_launched,
1115 [ - + ]: 39666 : queryDesc->plannedstmt->planOrigin);
1116 : : }
1117 : :
1118 [ - + ]: 56568 : if (prev_ExecutorEnd)
1119 : 0 : prev_ExecutorEnd(queryDesc);
1120 : : else
1121 : 56568 : standard_ExecutorEnd(queryDesc);
1122 : 56568 : }
1123 : :
1124 : : /*
1125 : : * ProcessUtility hook
1126 : : */
1127 : : static void
1128 : 37505 : pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
1129 : : bool readOnlyTree,
1130 : : ProcessUtilityContext context,
1131 : : ParamListInfo params, QueryEnvironment *queryEnv,
1132 : : DestReceiver *dest, QueryCompletion *qc)
1133 : : {
1134 : 37505 : Node *parsetree = pstmt->utilityStmt;
1135 : 37505 : int64 saved_queryId = pstmt->queryId;
1136 : 37505 : int saved_stmt_location = pstmt->stmt_location;
1137 : 37505 : int saved_stmt_len = pstmt->stmt_len;
1138 : 37505 : PlannedStmtOrigin saved_planOrigin = pstmt->planOrigin;
1139 [ + + + - : 37505 : bool enabled = pgss_track_utility && pgss_enabled(nesting_level);
+ + + - +
+ ]
1140 : :
1141 : : /*
1142 : : * Force utility statements to get queryId zero. We do this even in cases
1143 : : * where the statement contains an optimizable statement for which a
1144 : : * queryId could be derived (such as EXPLAIN or DECLARE CURSOR). For such
1145 : : * cases, runtime control will first go through ProcessUtility and then
1146 : : * the executor, and we don't want the executor hooks to do anything,
1147 : : * since we are already measuring the statement's costs at the utility
1148 : : * level.
1149 : : *
1150 : : * Note that this is only done if pg_stat_statements is enabled and
1151 : : * configured to track utility statements, in the unlikely possibility
1152 : : * that user configured another extension to handle utility statements
1153 : : * only.
1154 : : */
1155 [ + + ]: 37505 : if (enabled)
1156 : 31758 : pstmt->queryId = INT64CONST(0);
1157 : :
1158 : : /*
1159 : : * If it's an EXECUTE statement, we don't track it and don't increment the
1160 : : * nesting level. This allows the cycles to be charged to the underlying
1161 : : * PREPARE instead (by the Executor hooks), which is much more useful.
1162 : : *
1163 : : * We also don't track execution of PREPARE. If we did, we would get one
1164 : : * hash table entry for the PREPARE (with hash calculated from the query
1165 : : * string), and then a different one with the same query string (but hash
1166 : : * calculated from the query tree) would be used to accumulate costs of
1167 : : * ensuing EXECUTEs. This would be confusing. Since PREPARE doesn't
1168 : : * actually run the planner (only parse+rewrite), its costs are generally
1169 : : * pretty negligible and it seems okay to just ignore it.
1170 : : */
1171 [ + + ]: 37505 : if (enabled &&
1172 [ + + ]: 31758 : !IsA(parsetree, ExecuteStmt) &&
1173 [ + + ]: 28460 : !IsA(parsetree, PrepareStmt))
1174 : 25693 : {
1175 : : instr_time start;
1176 : : instr_time duration;
1177 : : uint64 rows;
1178 : : BufferUsage bufusage_start,
1179 : : bufusage;
1180 : : WalUsage walusage_start,
1181 : : walusage;
1182 : :
1183 : 28335 : bufusage_start = pgBufferUsage;
1184 : 28335 : walusage_start = pgWalUsage;
1185 : 28335 : INSTR_TIME_SET_CURRENT(start);
1186 : :
1187 : 28335 : nesting_level++;
1188 [ + + ]: 28335 : PG_TRY();
1189 : : {
1190 [ - + ]: 28335 : if (prev_ProcessUtility)
1191 : 0 : prev_ProcessUtility(pstmt, queryString, readOnlyTree,
1192 : : context, params, queryEnv,
1193 : : dest, qc);
1194 : : else
1195 : 28335 : standard_ProcessUtility(pstmt, queryString, readOnlyTree,
1196 : : context, params, queryEnv,
1197 : : dest, qc);
1198 : : }
1199 : 2642 : PG_FINALLY();
1200 : : {
1201 : 28335 : nesting_level--;
1202 : : }
1203 [ + + ]: 28335 : PG_END_TRY();
1204 : :
1205 : : /*
1206 : : * CAUTION: do not access the *pstmt data structure again below here.
1207 : : * If it was a ROLLBACK or similar, that data structure may have been
1208 : : * freed. We must copy everything we still need into local variables,
1209 : : * which we did above.
1210 : : *
1211 : : * For the same reason, we can't risk restoring pstmt->queryId to its
1212 : : * former value, which'd otherwise be a good idea.
1213 : : */
1214 : 25693 : pstmt = NULL;
1215 : :
1216 : 25693 : INSTR_TIME_SET_CURRENT(duration);
1217 : 25693 : INSTR_TIME_SUBTRACT(duration, start);
1218 : :
1219 : : /*
1220 : : * Track the total number of rows retrieved or affected by the utility
1221 : : * statements of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED
1222 : : * VIEW, REFRESH MATERIALIZED VIEW and SELECT INTO.
1223 : : */
1224 [ + + ]: 25690 : rows = (qc && (qc->commandTag == CMDTAG_COPY ||
1225 [ + + ]: 23960 : qc->commandTag == CMDTAG_FETCH ||
1226 [ + + ]: 23694 : qc->commandTag == CMDTAG_SELECT ||
1227 [ + + ]: 23499 : qc->commandTag == CMDTAG_REFRESH_MATERIALIZED_VIEW)) ?
1228 [ + + ]: 51383 : qc->nprocessed : 0;
1229 : :
1230 : : /* calc differences of buffer counters. */
1231 : 25693 : memset(&bufusage, 0, sizeof(BufferUsage));
1232 : 25693 : BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start);
1233 : :
1234 : : /* calc differences of WAL counters. */
1235 : 25693 : memset(&walusage, 0, sizeof(WalUsage));
1236 : 25693 : WalUsageAccumDiff(&walusage, &pgWalUsage, &walusage_start);
1237 : :
1238 : 25693 : pgss_store(queryString,
1239 : : saved_queryId,
1240 : : saved_stmt_location,
1241 : : saved_stmt_len,
1242 : : nesting_level == 0,
1243 : : PGSS_EXEC,
1244 : 25693 : INSTR_TIME_GET_MILLISEC(duration),
1245 : : rows,
1246 : : &bufusage,
1247 : : &walusage,
1248 : : NULL,
1249 : : NULL,
1250 : : 0,
1251 : : 0,
1252 : : saved_planOrigin);
1253 : : }
1254 : : else
1255 : : {
1256 : : /*
1257 : : * Even though we're not tracking execution time for this statement,
1258 : : * we must still increment the nesting level, to ensure that functions
1259 : : * evaluated within it are not seen as top-level calls. But don't do
1260 : : * so for EXECUTE; that way, when control reaches pgss_planner or
1261 : : * pgss_ExecutorStart, we will treat the costs as top-level if
1262 : : * appropriate. Likewise, don't bump for PREPARE, so that parse
1263 : : * analysis will treat the statement as top-level if appropriate.
1264 : : *
1265 : : * To be absolutely certain we don't mess up the nesting level,
1266 : : * evaluate the bump_level condition just once.
1267 : : */
1268 : 9170 : bool bump_level =
1269 [ + + ]: 15041 : !IsA(parsetree, ExecuteStmt) &&
1270 [ + + ]: 5871 : !IsA(parsetree, PrepareStmt);
1271 : :
1272 [ + + ]: 9170 : if (bump_level)
1273 : 5745 : nesting_level++;
1274 [ + + ]: 9170 : PG_TRY();
1275 : : {
1276 [ - + ]: 9170 : if (prev_ProcessUtility)
1277 : 0 : prev_ProcessUtility(pstmt, queryString, readOnlyTree,
1278 : : context, params, queryEnv,
1279 : : dest, qc);
1280 : : else
1281 : 9170 : standard_ProcessUtility(pstmt, queryString, readOnlyTree,
1282 : : context, params, queryEnv,
1283 : : dest, qc);
1284 : : }
1285 : 134 : PG_FINALLY();
1286 : : {
1287 [ + + ]: 9170 : if (bump_level)
1288 : 5745 : nesting_level--;
1289 : : }
1290 [ + + ]: 9170 : PG_END_TRY();
1291 : : }
1292 : 34729 : }
1293 : :
1294 : : /*
1295 : : * Store some statistics for a statement.
1296 : : *
1297 : : * If jstate is not NULL then we're trying to create an entry for which
1298 : : * we have no statistics as yet; we just want to record the normalized
1299 : : * query string. total_time, rows, bufusage and walusage are ignored in this
1300 : : * case.
1301 : : *
1302 : : * If kind is PGSS_PLAN or PGSS_EXEC, its value is used as the array position
1303 : : * for the arrays in the Counters field.
1304 : : */
1305 : : static void
1306 : 104831 : pgss_store(const char *query, int64 queryId,
1307 : : int query_location, int query_len,
1308 : : bool toplevel,
1309 : : pgssStoreKind kind,
1310 : : double total_time, uint64 rows,
1311 : : const BufferUsage *bufusage,
1312 : : const WalUsage *walusage,
1313 : : const struct JitInstrumentation *jitusage,
1314 : : const JumbleState *jstate,
1315 : : int parallel_workers_to_launch,
1316 : : int parallel_workers_launched,
1317 : : PlannedStmtOrigin planOrigin)
1318 : : {
1319 : : pgssHashKey key;
1320 : : pgssEntry *entry;
1321 : 104831 : char *norm_query = NULL;
1322 : 104831 : int encoding = GetDatabaseEncoding();
1323 : :
1324 : : Assert(query != NULL);
1325 : :
1326 : : /* Safety check... */
1327 [ + - - + ]: 104831 : if (!pgss || !pgss_hash)
1328 : 0 : return;
1329 : :
1330 : : /*
1331 : : * Nothing to do if compute_query_id isn't enabled and no other module
1332 : : * computed a query identifier.
1333 : : */
1334 [ - + ]: 104831 : if (queryId == INT64CONST(0))
1335 : 0 : return;
1336 : :
1337 : : /*
1338 : : * Confine our attention to the relevant part of the string, if the query
1339 : : * is a portion of a multi-statement source string, and update query
1340 : : * location and length if needed.
1341 : : */
1342 : 104831 : query = CleanQuerytext(query, &query_location, &query_len);
1343 : :
1344 : : /* Set up key for hashtable search */
1345 : :
1346 : : /* clear padding */
1347 : 104831 : memset(&key, 0, sizeof(pgssHashKey));
1348 : :
1349 : 104831 : key.userid = GetUserId();
1350 : 104831 : key.dbid = MyDatabaseId;
1351 : 104831 : key.queryid = queryId;
1352 : 104831 : key.toplevel = toplevel;
1353 : :
1354 : : /* Lookup the hash table entry with shared lock. */
1355 : 104831 : LWLockAcquire(&pgss->lock.lock, LW_SHARED);
1356 : :
1357 : 104831 : entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL);
1358 : :
1359 : : /* Create new entry, if not present */
1360 [ + + ]: 104831 : if (!entry)
1361 : : {
1362 : : Size query_offset;
1363 : : int gc_count;
1364 : : bool stored;
1365 : : bool do_gc;
1366 : :
1367 : : /*
1368 : : * Create a new, normalized query string if caller asked. We don't
1369 : : * need to hold the lock while doing this work. (Note: in any case,
1370 : : * it's possible that someone else creates a duplicate hashtable entry
1371 : : * in the interval where we don't hold the lock below. That case is
1372 : : * handled by entry_alloc.)
1373 : : */
1374 [ + + ]: 31346 : if (jstate)
1375 : : {
1376 : 11857 : LWLockRelease(&pgss->lock.lock);
1377 : 11857 : norm_query = generate_normalized_query(jstate, query,
1378 : : query_location,
1379 : : &query_len);
1380 : 11857 : LWLockAcquire(&pgss->lock.lock, LW_SHARED);
1381 : : }
1382 : :
1383 : : /* Append new query text to file with only shared lock held */
1384 [ + + ]: 31346 : stored = qtext_store(norm_query ? norm_query : query, query_len,
1385 : : &query_offset, &gc_count);
1386 : :
1387 : : /*
1388 : : * Determine whether we need to garbage collect external query texts
1389 : : * while the shared lock is still held. This micro-optimization
1390 : : * avoids taking the time to decide this while holding exclusive lock.
1391 : : */
1392 : 31346 : do_gc = need_gc_qtexts();
1393 : :
1394 : : /* Need exclusive lock to make a new hashtable entry - promote */
1395 : 31346 : LWLockRelease(&pgss->lock.lock);
1396 : 31346 : LWLockAcquire(&pgss->lock.lock, LW_EXCLUSIVE);
1397 : :
1398 : : /*
1399 : : * A garbage collection may have occurred while we weren't holding the
1400 : : * lock. In the unlikely event that this happens, the query text we
1401 : : * stored above will have been garbage collected, so write it again.
1402 : : * This should be infrequent enough that doing it while holding
1403 : : * exclusive lock isn't a performance problem.
1404 : : */
1405 [ + - - + ]: 31346 : if (!stored || pgss->gc_count != gc_count)
1406 [ # # ]: 0 : stored = qtext_store(norm_query ? norm_query : query, query_len,
1407 : : &query_offset, NULL);
1408 : :
1409 : : /* If we failed to write to the text file, give up */
1410 [ - + ]: 31346 : if (!stored)
1411 : 0 : goto done;
1412 : :
1413 : : /* OK to create a new hashtable entry */
1414 : 31346 : entry = entry_alloc(&key, query_offset, query_len, encoding,
1415 : : jstate != NULL);
1416 : :
1417 : : /* If needed, perform garbage collection while exclusive lock held */
1418 [ - + ]: 31346 : if (do_gc)
1419 : 0 : gc_qtexts();
1420 : : }
1421 : :
1422 : : /* Increment the counts, except when jstate is not NULL */
1423 [ + + ]: 104831 : if (!jstate)
1424 : : {
1425 : : Assert(kind == PGSS_PLAN || kind == PGSS_EXEC);
1426 : :
1427 : : /*
1428 : : * Grab the spinlock while updating the counters (see comment about
1429 : : * locking rules at the head of the file)
1430 : : */
1431 : 65509 : SpinLockAcquire(&entry->mutex);
1432 : :
1433 : : /* "Unstick" entry if it was previously sticky */
1434 [ + + ]: 65509 : if (IS_STICKY(entry->counters))
1435 : 30511 : entry->counters.usage = USAGE_INIT;
1436 : :
1437 : 65509 : entry->counters.calls[kind] += 1;
1438 : 65509 : entry->counters.total_time[kind] += total_time;
1439 : :
1440 [ + + ]: 65509 : if (entry->counters.calls[kind] == 1)
1441 : : {
1442 : 30605 : entry->counters.min_time[kind] = total_time;
1443 : 30605 : entry->counters.max_time[kind] = total_time;
1444 : 30605 : entry->counters.mean_time[kind] = total_time;
1445 : : }
1446 : : else
1447 : : {
1448 : : /*
1449 : : * Welford's method for accurately computing variance. See
1450 : : * <http://www.johndcook.com/blog/standard_deviation/>
1451 : : */
1452 : 34904 : double old_mean = entry->counters.mean_time[kind];
1453 : :
1454 : 34904 : entry->counters.mean_time[kind] +=
1455 : 34904 : (total_time - old_mean) / entry->counters.calls[kind];
1456 : 34904 : entry->counters.sum_var_time[kind] +=
1457 : 34904 : (total_time - old_mean) * (total_time - entry->counters.mean_time[kind]);
1458 : :
1459 : : /*
1460 : : * Calculate min and max time. min = 0 and max = 0 means that the
1461 : : * min/max statistics were reset
1462 : : */
1463 [ + + ]: 34904 : if (entry->counters.min_time[kind] == 0
1464 [ + + ]: 6 : && entry->counters.max_time[kind] == 0)
1465 : : {
1466 : 3 : entry->counters.min_time[kind] = total_time;
1467 : 3 : entry->counters.max_time[kind] = total_time;
1468 : : }
1469 : : else
1470 : : {
1471 [ + + ]: 34901 : if (entry->counters.min_time[kind] > total_time)
1472 : 6677 : entry->counters.min_time[kind] = total_time;
1473 [ + + ]: 34901 : if (entry->counters.max_time[kind] < total_time)
1474 : 3538 : entry->counters.max_time[kind] = total_time;
1475 : : }
1476 : : }
1477 : 65509 : entry->counters.rows += rows;
1478 : 65509 : entry->counters.shared_blks_hit += bufusage->shared_blks_hit;
1479 : 65509 : entry->counters.shared_blks_read += bufusage->shared_blks_read;
1480 : 65509 : entry->counters.shared_blks_dirtied += bufusage->shared_blks_dirtied;
1481 : 65509 : entry->counters.shared_blks_written += bufusage->shared_blks_written;
1482 : 65509 : entry->counters.local_blks_hit += bufusage->local_blks_hit;
1483 : 65509 : entry->counters.local_blks_read += bufusage->local_blks_read;
1484 : 65509 : entry->counters.local_blks_dirtied += bufusage->local_blks_dirtied;
1485 : 65509 : entry->counters.local_blks_written += bufusage->local_blks_written;
1486 : 65509 : entry->counters.temp_blks_read += bufusage->temp_blks_read;
1487 : 65509 : entry->counters.temp_blks_written += bufusage->temp_blks_written;
1488 : 65509 : entry->counters.shared_blk_read_time += INSTR_TIME_GET_MILLISEC(bufusage->shared_blk_read_time);
1489 : 65509 : entry->counters.shared_blk_write_time += INSTR_TIME_GET_MILLISEC(bufusage->shared_blk_write_time);
1490 : 65509 : entry->counters.local_blk_read_time += INSTR_TIME_GET_MILLISEC(bufusage->local_blk_read_time);
1491 : 65509 : entry->counters.local_blk_write_time += INSTR_TIME_GET_MILLISEC(bufusage->local_blk_write_time);
1492 : 65509 : entry->counters.temp_blk_read_time += INSTR_TIME_GET_MILLISEC(bufusage->temp_blk_read_time);
1493 : 65509 : entry->counters.temp_blk_write_time += INSTR_TIME_GET_MILLISEC(bufusage->temp_blk_write_time);
1494 : 65509 : entry->counters.usage += USAGE_EXEC(total_time);
1495 : 65509 : entry->counters.wal_records += walusage->wal_records;
1496 : 65509 : entry->counters.wal_fpi += walusage->wal_fpi;
1497 : 65509 : entry->counters.wal_bytes += walusage->wal_bytes;
1498 : 65509 : entry->counters.wal_buffers_full += walusage->wal_buffers_full;
1499 [ - + ]: 65509 : if (jitusage)
1500 : : {
1501 : 0 : entry->counters.jit_functions += jitusage->created_functions;
1502 : 0 : entry->counters.jit_generation_time += INSTR_TIME_GET_MILLISEC(jitusage->generation_counter);
1503 : :
1504 [ # # ]: 0 : if (INSTR_TIME_GET_MILLISEC(jitusage->deform_counter))
1505 : 0 : entry->counters.jit_deform_count++;
1506 : 0 : entry->counters.jit_deform_time += INSTR_TIME_GET_MILLISEC(jitusage->deform_counter);
1507 : :
1508 [ # # ]: 0 : if (INSTR_TIME_GET_MILLISEC(jitusage->inlining_counter))
1509 : 0 : entry->counters.jit_inlining_count++;
1510 : 0 : entry->counters.jit_inlining_time += INSTR_TIME_GET_MILLISEC(jitusage->inlining_counter);
1511 : :
1512 [ # # ]: 0 : if (INSTR_TIME_GET_MILLISEC(jitusage->optimization_counter))
1513 : 0 : entry->counters.jit_optimization_count++;
1514 : 0 : entry->counters.jit_optimization_time += INSTR_TIME_GET_MILLISEC(jitusage->optimization_counter);
1515 : :
1516 [ # # ]: 0 : if (INSTR_TIME_GET_MILLISEC(jitusage->emission_counter))
1517 : 0 : entry->counters.jit_emission_count++;
1518 : 0 : entry->counters.jit_emission_time += INSTR_TIME_GET_MILLISEC(jitusage->emission_counter);
1519 : : }
1520 : :
1521 : : /* parallel worker counters */
1522 : 65509 : entry->counters.parallel_workers_to_launch += parallel_workers_to_launch;
1523 : 65509 : entry->counters.parallel_workers_launched += parallel_workers_launched;
1524 : :
1525 : : /* plan cache counters */
1526 [ + + ]: 65509 : if (planOrigin == PLAN_STMT_CACHE_GENERIC)
1527 : 3102 : entry->counters.generic_plan_calls++;
1528 [ + + ]: 62407 : else if (planOrigin == PLAN_STMT_CACHE_CUSTOM)
1529 : 384 : entry->counters.custom_plan_calls++;
1530 : :
1531 : 65509 : SpinLockRelease(&entry->mutex);
1532 : : }
1533 : :
1534 : 39322 : done:
1535 : 104831 : LWLockRelease(&pgss->lock.lock);
1536 : :
1537 : : /* We postpone this clean-up until we're out of the lock */
1538 [ + + ]: 104831 : if (norm_query)
1539 : 11857 : pfree(norm_query);
1540 : : }
1541 : :
1542 : : /*
1543 : : * Reset statement statistics corresponding to userid, dbid, and queryid.
1544 : : */
1545 : : Datum
1546 : 1 : pg_stat_statements_reset_1_7(PG_FUNCTION_ARGS)
1547 : : {
1548 : : Oid userid;
1549 : : Oid dbid;
1550 : : int64 queryid;
1551 : :
1552 : 1 : userid = PG_GETARG_OID(0);
1553 : 1 : dbid = PG_GETARG_OID(1);
1554 : 1 : queryid = PG_GETARG_INT64(2);
1555 : :
1556 : 1 : entry_reset(userid, dbid, queryid, false);
1557 : :
1558 : 1 : PG_RETURN_VOID();
1559 : : }
1560 : :
1561 : : Datum
1562 : 139 : pg_stat_statements_reset_1_11(PG_FUNCTION_ARGS)
1563 : : {
1564 : : Oid userid;
1565 : : Oid dbid;
1566 : : int64 queryid;
1567 : : bool minmax_only;
1568 : :
1569 : 139 : userid = PG_GETARG_OID(0);
1570 : 139 : dbid = PG_GETARG_OID(1);
1571 : 139 : queryid = PG_GETARG_INT64(2);
1572 : 139 : minmax_only = PG_GETARG_BOOL(3);
1573 : :
1574 : 139 : PG_RETURN_TIMESTAMPTZ(entry_reset(userid, dbid, queryid, minmax_only));
1575 : : }
1576 : :
1577 : : /*
1578 : : * Reset statement statistics.
1579 : : */
1580 : : Datum
1581 : 1 : pg_stat_statements_reset(PG_FUNCTION_ARGS)
1582 : : {
1583 : 1 : entry_reset(0, 0, 0, false);
1584 : :
1585 : 1 : PG_RETURN_VOID();
1586 : : }
1587 : :
1588 : : /* Number of output arguments (columns) for various API versions */
1589 : : #define PG_STAT_STATEMENTS_COLS_V1_0 14
1590 : : #define PG_STAT_STATEMENTS_COLS_V1_1 18
1591 : : #define PG_STAT_STATEMENTS_COLS_V1_2 19
1592 : : #define PG_STAT_STATEMENTS_COLS_V1_3 23
1593 : : #define PG_STAT_STATEMENTS_COLS_V1_8 32
1594 : : #define PG_STAT_STATEMENTS_COLS_V1_9 33
1595 : : #define PG_STAT_STATEMENTS_COLS_V1_10 43
1596 : : #define PG_STAT_STATEMENTS_COLS_V1_11 49
1597 : : #define PG_STAT_STATEMENTS_COLS_V1_12 52
1598 : : #define PG_STAT_STATEMENTS_COLS_V1_13 54
1599 : : #define PG_STAT_STATEMENTS_COLS 54 /* maximum of above */
1600 : :
1601 : : /*
1602 : : * Retrieve statement statistics.
1603 : : *
1604 : : * The SQL API of this function has changed multiple times, and will likely
1605 : : * do so again in future. To support the case where a newer version of this
1606 : : * loadable module is being used with an old SQL declaration of the function,
1607 : : * we continue to support the older API versions. For 1.2 and later, the
1608 : : * expected API version is identified by embedding it in the C name of the
1609 : : * function. Unfortunately we weren't bright enough to do that for 1.1.
1610 : : */
1611 : : Datum
1612 : 149 : pg_stat_statements_1_13(PG_FUNCTION_ARGS)
1613 : : {
1614 : 149 : bool showtext = PG_GETARG_BOOL(0);
1615 : :
1616 : 149 : pg_stat_statements_internal(fcinfo, PGSS_V1_13, showtext);
1617 : :
1618 : 149 : return (Datum) 0;
1619 : : }
1620 : :
1621 : : Datum
1622 : 1 : pg_stat_statements_1_12(PG_FUNCTION_ARGS)
1623 : : {
1624 : 1 : bool showtext = PG_GETARG_BOOL(0);
1625 : :
1626 : 1 : pg_stat_statements_internal(fcinfo, PGSS_V1_12, showtext);
1627 : :
1628 : 1 : return (Datum) 0;
1629 : : }
1630 : :
1631 : : Datum
1632 : 1 : pg_stat_statements_1_11(PG_FUNCTION_ARGS)
1633 : : {
1634 : 1 : bool showtext = PG_GETARG_BOOL(0);
1635 : :
1636 : 1 : pg_stat_statements_internal(fcinfo, PGSS_V1_11, showtext);
1637 : :
1638 : 1 : return (Datum) 0;
1639 : : }
1640 : :
1641 : : Datum
1642 : 1 : pg_stat_statements_1_10(PG_FUNCTION_ARGS)
1643 : : {
1644 : 1 : bool showtext = PG_GETARG_BOOL(0);
1645 : :
1646 : 1 : pg_stat_statements_internal(fcinfo, PGSS_V1_10, showtext);
1647 : :
1648 : 1 : return (Datum) 0;
1649 : : }
1650 : :
1651 : : Datum
1652 : 1 : pg_stat_statements_1_9(PG_FUNCTION_ARGS)
1653 : : {
1654 : 1 : bool showtext = PG_GETARG_BOOL(0);
1655 : :
1656 : 1 : pg_stat_statements_internal(fcinfo, PGSS_V1_9, showtext);
1657 : :
1658 : 1 : return (Datum) 0;
1659 : : }
1660 : :
1661 : : Datum
1662 : 1 : pg_stat_statements_1_8(PG_FUNCTION_ARGS)
1663 : : {
1664 : 1 : bool showtext = PG_GETARG_BOOL(0);
1665 : :
1666 : 1 : pg_stat_statements_internal(fcinfo, PGSS_V1_8, showtext);
1667 : :
1668 : 1 : return (Datum) 0;
1669 : : }
1670 : :
1671 : : Datum
1672 : 1 : pg_stat_statements_1_3(PG_FUNCTION_ARGS)
1673 : : {
1674 : 1 : bool showtext = PG_GETARG_BOOL(0);
1675 : :
1676 : 1 : pg_stat_statements_internal(fcinfo, PGSS_V1_3, showtext);
1677 : :
1678 : 1 : return (Datum) 0;
1679 : : }
1680 : :
1681 : : Datum
1682 : 0 : pg_stat_statements_1_2(PG_FUNCTION_ARGS)
1683 : : {
1684 : 0 : bool showtext = PG_GETARG_BOOL(0);
1685 : :
1686 : 0 : pg_stat_statements_internal(fcinfo, PGSS_V1_2, showtext);
1687 : :
1688 : 0 : return (Datum) 0;
1689 : : }
1690 : :
1691 : : /*
1692 : : * Legacy entry point for pg_stat_statements() API versions 1.0 and 1.1.
1693 : : * This can be removed someday, perhaps.
1694 : : */
1695 : : Datum
1696 : 0 : pg_stat_statements(PG_FUNCTION_ARGS)
1697 : : {
1698 : : /* If it's really API 1.1, we'll figure that out below */
1699 : 0 : pg_stat_statements_internal(fcinfo, PGSS_V1_0, true);
1700 : :
1701 : 0 : return (Datum) 0;
1702 : : }
1703 : :
1704 : : /* Common code for all versions of pg_stat_statements() */
1705 : : static void
1706 : 155 : pg_stat_statements_internal(FunctionCallInfo fcinfo,
1707 : : pgssVersion api_version,
1708 : : bool showtext)
1709 : : {
1710 : 155 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
1711 : 155 : Oid userid = GetUserId();
1712 : 155 : bool is_allowed_role = false;
1713 : 155 : char *qbuffer = NULL;
1714 : 155 : Size qbuffer_size = 0;
1715 : 155 : Size extent = 0;
1716 : 155 : int gc_count = 0;
1717 : : HASH_SEQ_STATUS hash_seq;
1718 : : pgssEntry *entry;
1719 : :
1720 : : /*
1721 : : * Superusers or roles with the privileges of pg_read_all_stats members
1722 : : * are allowed
1723 : : */
1724 : 155 : is_allowed_role = has_privs_of_role(userid, ROLE_PG_READ_ALL_STATS);
1725 : :
1726 : : /* hash table must exist already */
1727 [ + - - + ]: 155 : if (!pgss || !pgss_hash)
1728 [ # # ]: 0 : ereport(ERROR,
1729 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1730 : : errmsg("pg_stat_statements must be loaded via \"shared_preload_libraries\"")));
1731 : :
1732 : 155 : InitMaterializedSRF(fcinfo, 0);
1733 : :
1734 : : /*
1735 : : * Check we have the expected number of output arguments. Aside from
1736 : : * being a good safety check, we need a kluge here to detect API version
1737 : : * 1.1, which was wedged into the code in an ill-considered way.
1738 : : */
1739 [ - - - + : 155 : switch (rsinfo->setDesc->natts)
+ + + + +
+ - ]
1740 : : {
1741 : 0 : case PG_STAT_STATEMENTS_COLS_V1_0:
1742 [ # # ]: 0 : if (api_version != PGSS_V1_0)
1743 [ # # ]: 0 : elog(ERROR, "incorrect number of output arguments");
1744 : 0 : break;
1745 : 0 : case PG_STAT_STATEMENTS_COLS_V1_1:
1746 : : /* pg_stat_statements() should have told us 1.0 */
1747 [ # # ]: 0 : if (api_version != PGSS_V1_0)
1748 [ # # ]: 0 : elog(ERROR, "incorrect number of output arguments");
1749 : 0 : api_version = PGSS_V1_1;
1750 : 0 : break;
1751 : 0 : case PG_STAT_STATEMENTS_COLS_V1_2:
1752 [ # # ]: 0 : if (api_version != PGSS_V1_2)
1753 [ # # ]: 0 : elog(ERROR, "incorrect number of output arguments");
1754 : 0 : break;
1755 : 1 : case PG_STAT_STATEMENTS_COLS_V1_3:
1756 [ - + ]: 1 : if (api_version != PGSS_V1_3)
1757 [ # # ]: 0 : elog(ERROR, "incorrect number of output arguments");
1758 : 1 : break;
1759 : 1 : case PG_STAT_STATEMENTS_COLS_V1_8:
1760 [ - + ]: 1 : if (api_version != PGSS_V1_8)
1761 [ # # ]: 0 : elog(ERROR, "incorrect number of output arguments");
1762 : 1 : break;
1763 : 1 : case PG_STAT_STATEMENTS_COLS_V1_9:
1764 [ - + ]: 1 : if (api_version != PGSS_V1_9)
1765 [ # # ]: 0 : elog(ERROR, "incorrect number of output arguments");
1766 : 1 : break;
1767 : 1 : case PG_STAT_STATEMENTS_COLS_V1_10:
1768 [ - + ]: 1 : if (api_version != PGSS_V1_10)
1769 [ # # ]: 0 : elog(ERROR, "incorrect number of output arguments");
1770 : 1 : break;
1771 : 1 : case PG_STAT_STATEMENTS_COLS_V1_11:
1772 [ - + ]: 1 : if (api_version != PGSS_V1_11)
1773 [ # # ]: 0 : elog(ERROR, "incorrect number of output arguments");
1774 : 1 : break;
1775 : 1 : case PG_STAT_STATEMENTS_COLS_V1_12:
1776 [ - + ]: 1 : if (api_version != PGSS_V1_12)
1777 [ # # ]: 0 : elog(ERROR, "incorrect number of output arguments");
1778 : 1 : break;
1779 : 149 : case PG_STAT_STATEMENTS_COLS_V1_13:
1780 [ - + ]: 149 : if (api_version != PGSS_V1_13)
1781 [ # # ]: 0 : elog(ERROR, "incorrect number of output arguments");
1782 : 149 : break;
1783 : 0 : default:
1784 [ # # ]: 0 : elog(ERROR, "incorrect number of output arguments");
1785 : : }
1786 : :
1787 : : /*
1788 : : * We'd like to load the query text file (if needed) while not holding any
1789 : : * lock on pgss->lock. In the worst case we'll have to do this again
1790 : : * after we have the lock, but it's unlikely enough to make this a win
1791 : : * despite occasional duplicated work. We need to reload if anybody
1792 : : * writes to the file (either a retail qtext_store(), or a garbage
1793 : : * collection) between this point and where we've gotten shared lock. If
1794 : : * a qtext_store is actually in progress when we look, we might as well
1795 : : * skip the speculative load entirely.
1796 : : */
1797 [ + - ]: 155 : if (showtext)
1798 : : {
1799 : : int n_writers;
1800 : :
1801 : : /* Take the mutex so we can examine variables */
1802 : 155 : SpinLockAcquire(&pgss->mutex);
1803 : 155 : extent = pgss->extent;
1804 : 155 : n_writers = pgss->n_writers;
1805 : 155 : gc_count = pgss->gc_count;
1806 : 155 : SpinLockRelease(&pgss->mutex);
1807 : :
1808 : : /* No point in loading file now if there are active writers */
1809 [ + - ]: 155 : if (n_writers == 0)
1810 : 155 : qbuffer = qtext_load_file(&qbuffer_size);
1811 : : }
1812 : :
1813 : : /*
1814 : : * Get shared lock, load or reload the query text file if we must, and
1815 : : * iterate over the hashtable entries.
1816 : : *
1817 : : * With a large hash table, we might be holding the lock rather longer
1818 : : * than one could wish. However, this only blocks creation of new hash
1819 : : * table entries, and the larger the hash table the less likely that is to
1820 : : * be needed. So we can hope this is okay. Perhaps someday we'll decide
1821 : : * we need to partition the hash table to limit the time spent holding any
1822 : : * one lock.
1823 : : */
1824 : 155 : LWLockAcquire(&pgss->lock.lock, LW_SHARED);
1825 : :
1826 [ + - ]: 155 : if (showtext)
1827 : : {
1828 : : /*
1829 : : * Here it is safe to examine extent and gc_count without taking the
1830 : : * mutex. Note that although other processes might change
1831 : : * pgss->extent just after we look at it, the strings they then write
1832 : : * into the file cannot yet be referenced in the hashtable, so we
1833 : : * don't care whether we see them or not.
1834 : : *
1835 : : * If qtext_load_file fails, we just press on; we'll return NULL for
1836 : : * every query text.
1837 : : */
1838 [ + - ]: 155 : if (qbuffer == NULL ||
1839 [ + - ]: 155 : pgss->extent != extent ||
1840 [ - + ]: 155 : pgss->gc_count != gc_count)
1841 : : {
1842 [ # # ]: 0 : if (qbuffer)
1843 : 0 : pfree(qbuffer);
1844 : 0 : qbuffer = qtext_load_file(&qbuffer_size);
1845 : : }
1846 : : }
1847 : :
1848 : 155 : hash_seq_init(&hash_seq, pgss_hash);
1849 [ + + ]: 29742 : while ((entry = hash_seq_search(&hash_seq)) != NULL)
1850 : : {
1851 : : Datum values[PG_STAT_STATEMENTS_COLS];
1852 : : bool nulls[PG_STAT_STATEMENTS_COLS];
1853 : 29587 : int i = 0;
1854 : : Counters tmp;
1855 : : double stddev;
1856 : 29587 : int64 queryid = entry->key.queryid;
1857 : : TimestampTz stats_since;
1858 : : TimestampTz minmax_stats_since;
1859 : :
1860 : 29587 : memset(values, 0, sizeof(values));
1861 : 29587 : memset(nulls, 0, sizeof(nulls));
1862 : :
1863 : 29587 : values[i++] = ObjectIdGetDatum(entry->key.userid);
1864 : 29587 : values[i++] = ObjectIdGetDatum(entry->key.dbid);
1865 [ + + ]: 29587 : if (api_version >= PGSS_V1_9)
1866 : 29575 : values[i++] = BoolGetDatum(entry->key.toplevel);
1867 : :
1868 [ + + + + ]: 29587 : if (is_allowed_role || entry->key.userid == userid)
1869 : : {
1870 [ + - ]: 29583 : if (api_version >= PGSS_V1_2)
1871 : 29583 : values[i++] = Int64GetDatumFast(queryid);
1872 : :
1873 [ + - ]: 29583 : if (showtext)
1874 : : {
1875 : 29583 : char *qstr = qtext_fetch(entry->query_offset,
1876 : : entry->query_len,
1877 : : qbuffer,
1878 : : qbuffer_size);
1879 : :
1880 [ + - ]: 29583 : if (qstr)
1881 : : {
1882 : : char *enc;
1883 : :
1884 : 29583 : enc = pg_any_to_server(qstr,
1885 : : entry->query_len,
1886 : : entry->encoding);
1887 : :
1888 : 29583 : values[i++] = CStringGetTextDatum(enc);
1889 : :
1890 [ - + ]: 29583 : if (enc != qstr)
1891 : 0 : pfree(enc);
1892 : : }
1893 : : else
1894 : : {
1895 : : /* Just return a null if we fail to find the text */
1896 : 0 : nulls[i++] = true;
1897 : : }
1898 : : }
1899 : : else
1900 : : {
1901 : : /* Query text not requested */
1902 : 0 : nulls[i++] = true;
1903 : : }
1904 : : }
1905 : : else
1906 : : {
1907 : : /* Don't show queryid */
1908 [ + - ]: 4 : if (api_version >= PGSS_V1_2)
1909 : 4 : nulls[i++] = true;
1910 : :
1911 : : /*
1912 : : * Don't show query text, but hint as to the reason for not doing
1913 : : * so if it was requested
1914 : : */
1915 [ + - ]: 4 : if (showtext)
1916 : 4 : values[i++] = CStringGetTextDatum("<insufficient privilege>");
1917 : : else
1918 : 0 : nulls[i++] = true;
1919 : : }
1920 : :
1921 : : /* copy counters to a local variable to keep locking time short */
1922 : 29587 : SpinLockAcquire(&entry->mutex);
1923 : 29587 : tmp = entry->counters;
1924 : 29587 : SpinLockRelease(&entry->mutex);
1925 : :
1926 : : /*
1927 : : * The spinlock is not required when reading these two as they are
1928 : : * always updated when holding pgss->lock exclusively.
1929 : : */
1930 : 29587 : stats_since = entry->stats_since;
1931 : 29587 : minmax_stats_since = entry->minmax_stats_since;
1932 : :
1933 : : /* Skip entry if unexecuted (ie, it's a pending "sticky" entry) */
1934 [ + + ]: 29587 : if (IS_STICKY(tmp))
1935 : 48 : continue;
1936 : :
1937 : : /* Note that we rely on PGSS_PLAN being 0 and PGSS_EXEC being 1. */
1938 [ + + ]: 88617 : for (int kind = 0; kind < PGSS_NUMKIND; kind++)
1939 : : {
1940 [ + + + + ]: 59078 : if (kind == PGSS_EXEC || api_version >= PGSS_V1_8)
1941 : : {
1942 : 59074 : values[i++] = Int64GetDatumFast(tmp.calls[kind]);
1943 : 59074 : values[i++] = Float8GetDatumFast(tmp.total_time[kind]);
1944 : : }
1945 : :
1946 [ + + - + : 59078 : if ((kind == PGSS_EXEC && api_version >= PGSS_V1_3) ||
+ + ]
1947 : : api_version >= PGSS_V1_8)
1948 : : {
1949 : 59074 : values[i++] = Float8GetDatumFast(tmp.min_time[kind]);
1950 : 59074 : values[i++] = Float8GetDatumFast(tmp.max_time[kind]);
1951 : 59074 : values[i++] = Float8GetDatumFast(tmp.mean_time[kind]);
1952 : :
1953 : : /*
1954 : : * Note we are calculating the population variance here, not
1955 : : * the sample variance, as we have data for the whole
1956 : : * population, so Bessel's correction is not used, and we
1957 : : * don't divide by tmp.calls - 1.
1958 : : */
1959 [ + + ]: 59074 : if (tmp.calls[kind] > 1)
1960 : 5386 : stddev = sqrt(tmp.sum_var_time[kind] / tmp.calls[kind]);
1961 : : else
1962 : 53688 : stddev = 0.0;
1963 : 59074 : values[i++] = Float8GetDatumFast(stddev);
1964 : : }
1965 : : }
1966 : 29539 : values[i++] = Int64GetDatumFast(tmp.rows);
1967 : 29539 : values[i++] = Int64GetDatumFast(tmp.shared_blks_hit);
1968 : 29539 : values[i++] = Int64GetDatumFast(tmp.shared_blks_read);
1969 [ + - ]: 29539 : if (api_version >= PGSS_V1_1)
1970 : 29539 : values[i++] = Int64GetDatumFast(tmp.shared_blks_dirtied);
1971 : 29539 : values[i++] = Int64GetDatumFast(tmp.shared_blks_written);
1972 : 29539 : values[i++] = Int64GetDatumFast(tmp.local_blks_hit);
1973 : 29539 : values[i++] = Int64GetDatumFast(tmp.local_blks_read);
1974 [ + - ]: 29539 : if (api_version >= PGSS_V1_1)
1975 : 29539 : values[i++] = Int64GetDatumFast(tmp.local_blks_dirtied);
1976 : 29539 : values[i++] = Int64GetDatumFast(tmp.local_blks_written);
1977 : 29539 : values[i++] = Int64GetDatumFast(tmp.temp_blks_read);
1978 : 29539 : values[i++] = Int64GetDatumFast(tmp.temp_blks_written);
1979 [ + - ]: 29539 : if (api_version >= PGSS_V1_1)
1980 : : {
1981 : 29539 : values[i++] = Float8GetDatumFast(tmp.shared_blk_read_time);
1982 : 29539 : values[i++] = Float8GetDatumFast(tmp.shared_blk_write_time);
1983 : : }
1984 [ + + ]: 29539 : if (api_version >= PGSS_V1_11)
1985 : : {
1986 : 29511 : values[i++] = Float8GetDatumFast(tmp.local_blk_read_time);
1987 : 29511 : values[i++] = Float8GetDatumFast(tmp.local_blk_write_time);
1988 : : }
1989 [ + + ]: 29539 : if (api_version >= PGSS_V1_10)
1990 : : {
1991 : 29520 : values[i++] = Float8GetDatumFast(tmp.temp_blk_read_time);
1992 : 29520 : values[i++] = Float8GetDatumFast(tmp.temp_blk_write_time);
1993 : : }
1994 [ + + ]: 29539 : if (api_version >= PGSS_V1_8)
1995 : : {
1996 : : char buf[256];
1997 : : Datum wal_bytes;
1998 : :
1999 : 29535 : values[i++] = Int64GetDatumFast(tmp.wal_records);
2000 : 29535 : values[i++] = Int64GetDatumFast(tmp.wal_fpi);
2001 : :
2002 : 29535 : snprintf(buf, sizeof buf, UINT64_FORMAT, tmp.wal_bytes);
2003 : :
2004 : : /* Convert to numeric. */
2005 : 29535 : wal_bytes = DirectFunctionCall3(numeric_in,
2006 : : CStringGetDatum(buf),
2007 : : ObjectIdGetDatum(0),
2008 : : Int32GetDatum(-1));
2009 : 29535 : values[i++] = wal_bytes;
2010 : : }
2011 [ + + ]: 29539 : if (api_version >= PGSS_V1_12)
2012 : : {
2013 : 29501 : values[i++] = Int64GetDatumFast(tmp.wal_buffers_full);
2014 : : }
2015 [ + + ]: 29539 : if (api_version >= PGSS_V1_10)
2016 : : {
2017 : 29520 : values[i++] = Int64GetDatumFast(tmp.jit_functions);
2018 : 29520 : values[i++] = Float8GetDatumFast(tmp.jit_generation_time);
2019 : 29520 : values[i++] = Int64GetDatumFast(tmp.jit_inlining_count);
2020 : 29520 : values[i++] = Float8GetDatumFast(tmp.jit_inlining_time);
2021 : 29520 : values[i++] = Int64GetDatumFast(tmp.jit_optimization_count);
2022 : 29520 : values[i++] = Float8GetDatumFast(tmp.jit_optimization_time);
2023 : 29520 : values[i++] = Int64GetDatumFast(tmp.jit_emission_count);
2024 : 29520 : values[i++] = Float8GetDatumFast(tmp.jit_emission_time);
2025 : : }
2026 [ + + ]: 29539 : if (api_version >= PGSS_V1_11)
2027 : : {
2028 : 29511 : values[i++] = Int64GetDatumFast(tmp.jit_deform_count);
2029 : 29511 : values[i++] = Float8GetDatumFast(tmp.jit_deform_time);
2030 : : }
2031 [ + + ]: 29539 : if (api_version >= PGSS_V1_12)
2032 : : {
2033 : 29501 : values[i++] = Int64GetDatumFast(tmp.parallel_workers_to_launch);
2034 : 29501 : values[i++] = Int64GetDatumFast(tmp.parallel_workers_launched);
2035 : : }
2036 [ + + ]: 29539 : if (api_version >= PGSS_V1_13)
2037 : : {
2038 : 29496 : values[i++] = Int64GetDatumFast(tmp.generic_plan_calls);
2039 : 29496 : values[i++] = Int64GetDatumFast(tmp.custom_plan_calls);
2040 : : }
2041 [ + + ]: 29539 : if (api_version >= PGSS_V1_11)
2042 : : {
2043 : 29511 : values[i++] = TimestampTzGetDatum(stats_since);
2044 : 29511 : values[i++] = TimestampTzGetDatum(minmax_stats_since);
2045 : : }
2046 : :
2047 : : Assert(i == (api_version == PGSS_V1_0 ? PG_STAT_STATEMENTS_COLS_V1_0 :
2048 : : api_version == PGSS_V1_1 ? PG_STAT_STATEMENTS_COLS_V1_1 :
2049 : : api_version == PGSS_V1_2 ? PG_STAT_STATEMENTS_COLS_V1_2 :
2050 : : api_version == PGSS_V1_3 ? PG_STAT_STATEMENTS_COLS_V1_3 :
2051 : : api_version == PGSS_V1_8 ? PG_STAT_STATEMENTS_COLS_V1_8 :
2052 : : api_version == PGSS_V1_9 ? PG_STAT_STATEMENTS_COLS_V1_9 :
2053 : : api_version == PGSS_V1_10 ? PG_STAT_STATEMENTS_COLS_V1_10 :
2054 : : api_version == PGSS_V1_11 ? PG_STAT_STATEMENTS_COLS_V1_11 :
2055 : : api_version == PGSS_V1_12 ? PG_STAT_STATEMENTS_COLS_V1_12 :
2056 : : api_version == PGSS_V1_13 ? PG_STAT_STATEMENTS_COLS_V1_13 :
2057 : : -1 /* fail if you forget to update this assert */ ));
2058 : :
2059 : 29539 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
2060 : : }
2061 : :
2062 : 155 : LWLockRelease(&pgss->lock.lock);
2063 : :
2064 [ + - ]: 155 : if (qbuffer)
2065 : 155 : pfree(qbuffer);
2066 : 155 : }
2067 : :
2068 : : /* Number of output arguments (columns) for pg_stat_statements_info */
2069 : : #define PG_STAT_STATEMENTS_INFO_COLS 2
2070 : :
2071 : : /*
2072 : : * Return statistics of pg_stat_statements.
2073 : : */
2074 : : Datum
2075 : 3 : pg_stat_statements_info(PG_FUNCTION_ARGS)
2076 : : {
2077 : : pgssGlobalStats stats;
2078 : : TupleDesc tupdesc;
2079 : 3 : Datum values[PG_STAT_STATEMENTS_INFO_COLS] = {0};
2080 : 3 : bool nulls[PG_STAT_STATEMENTS_INFO_COLS] = {0};
2081 : :
2082 [ + - - + ]: 3 : if (!pgss || !pgss_hash)
2083 [ # # ]: 0 : ereport(ERROR,
2084 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
2085 : : errmsg("pg_stat_statements must be loaded via \"shared_preload_libraries\"")));
2086 : :
2087 : : /* Build a tuple descriptor for our result type */
2088 [ - + ]: 3 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
2089 [ # # ]: 0 : elog(ERROR, "return type must be a row type");
2090 : :
2091 : : /* Read global statistics for pg_stat_statements */
2092 : 3 : SpinLockAcquire(&pgss->mutex);
2093 : 3 : stats = pgss->stats;
2094 : 3 : SpinLockRelease(&pgss->mutex);
2095 : :
2096 : 3 : values[0] = Int64GetDatum(stats.dealloc);
2097 : 3 : values[1] = TimestampTzGetDatum(stats.stats_reset);
2098 : :
2099 : 3 : PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
2100 : : }
2101 : :
2102 : : /*
2103 : : * Allocate a new hashtable entry.
2104 : : * caller must hold an exclusive lock on pgss->lock
2105 : : *
2106 : : * "query" need not be null-terminated; we rely on query_len instead
2107 : : *
2108 : : * If "sticky" is true, make the new entry artificially sticky so that it will
2109 : : * probably still be there when the query finishes execution. We do this by
2110 : : * giving it a median usage value rather than the normal value. (Strictly
2111 : : * speaking, query strings are normalized on a best effort basis, though it
2112 : : * would be difficult to demonstrate this even under artificial conditions.)
2113 : : *
2114 : : * Note: despite needing exclusive lock, it's not an error for the target
2115 : : * entry to already exist. This is because pgss_store releases and
2116 : : * reacquires lock after failing to find a match; so someone else could
2117 : : * have made the entry while we waited to get exclusive lock.
2118 : : */
2119 : : static pgssEntry *
2120 : 59788 : entry_alloc(pgssHashKey *key, Size query_offset, int query_len, int encoding,
2121 : : bool sticky)
2122 : : {
2123 : : pgssEntry *entry;
2124 : : bool found;
2125 : :
2126 : : /* Make space if needed */
2127 [ - + ]: 59788 : while (hash_get_num_entries(pgss_hash) >= (int64) pgss_max)
2128 : 0 : entry_dealloc();
2129 : :
2130 : : /* Find or create an entry with desired hash code */
2131 : 59788 : entry = (pgssEntry *) hash_search(pgss_hash, key, HASH_ENTER, &found);
2132 : :
2133 [ + - ]: 59788 : if (!found)
2134 : : {
2135 : : /* New entry, initialize it */
2136 : :
2137 : : /* reset the statistics */
2138 : 59788 : memset(&entry->counters, 0, sizeof(Counters));
2139 : : /* set the appropriate initial usage count */
2140 [ + + ]: 59788 : entry->counters.usage = sticky ? pgss->cur_median_usage : USAGE_INIT;
2141 : : /* re-initialize the mutex each time ... we assume no one using it */
2142 : 59788 : SpinLockInit(&entry->mutex);
2143 : : /* ... and don't forget the query text metadata */
2144 : : Assert(query_len >= 0);
2145 : 59788 : entry->query_offset = query_offset;
2146 : 59788 : entry->query_len = query_len;
2147 : 59788 : entry->encoding = encoding;
2148 : 59788 : entry->stats_since = GetCurrentTimestamp();
2149 : 59788 : entry->minmax_stats_since = entry->stats_since;
2150 : : }
2151 : :
2152 : 59788 : return entry;
2153 : : }
2154 : :
2155 : : /*
2156 : : * qsort comparator for sorting into increasing usage order
2157 : : */
2158 : : static int
2159 : 0 : entry_cmp(const void *lhs, const void *rhs)
2160 : : {
2161 : 0 : double l_usage = (*(pgssEntry *const *) lhs)->counters.usage;
2162 : 0 : double r_usage = (*(pgssEntry *const *) rhs)->counters.usage;
2163 : :
2164 [ # # ]: 0 : if (l_usage < r_usage)
2165 : 0 : return -1;
2166 [ # # ]: 0 : else if (l_usage > r_usage)
2167 : 0 : return +1;
2168 : : else
2169 : 0 : return 0;
2170 : : }
2171 : :
2172 : : /*
2173 : : * Deallocate least-used entries.
2174 : : *
2175 : : * Caller must hold an exclusive lock on pgss->lock.
2176 : : */
2177 : : static void
2178 : 0 : entry_dealloc(void)
2179 : : {
2180 : : HASH_SEQ_STATUS hash_seq;
2181 : : pgssEntry **entries;
2182 : : pgssEntry *entry;
2183 : : int nvictims;
2184 : : int i;
2185 : : Size tottextlen;
2186 : : int nvalidtexts;
2187 : :
2188 : : /*
2189 : : * Sort entries by usage and deallocate USAGE_DEALLOC_PERCENT of them.
2190 : : * While we're scanning the table, apply the decay factor to the usage
2191 : : * values, and update the mean query length.
2192 : : *
2193 : : * Note that the mean query length is almost immediately obsolete, since
2194 : : * we compute it before not after discarding the least-used entries.
2195 : : * Hopefully, that doesn't affect the mean too much; it doesn't seem worth
2196 : : * making two passes to get a more current result. Likewise, the new
2197 : : * cur_median_usage includes the entries we're about to zap.
2198 : : */
2199 : :
2200 : 0 : entries = palloc_array(pgssEntry *, hash_get_num_entries(pgss_hash));
2201 : :
2202 : 0 : i = 0;
2203 : 0 : tottextlen = 0;
2204 : 0 : nvalidtexts = 0;
2205 : :
2206 : 0 : hash_seq_init(&hash_seq, pgss_hash);
2207 [ # # ]: 0 : while ((entry = hash_seq_search(&hash_seq)) != NULL)
2208 : : {
2209 : 0 : entries[i++] = entry;
2210 : : /* "Sticky" entries get a different usage decay rate. */
2211 [ # # ]: 0 : if (IS_STICKY(entry->counters))
2212 : 0 : entry->counters.usage *= STICKY_DECREASE_FACTOR;
2213 : : else
2214 : 0 : entry->counters.usage *= USAGE_DECREASE_FACTOR;
2215 : : /* In the mean length computation, ignore dropped texts. */
2216 [ # # ]: 0 : if (entry->query_len >= 0)
2217 : : {
2218 : 0 : tottextlen += entry->query_len + 1;
2219 : 0 : nvalidtexts++;
2220 : : }
2221 : : }
2222 : :
2223 : : /* Sort into increasing order by usage */
2224 : 0 : qsort(entries, i, sizeof(pgssEntry *), entry_cmp);
2225 : :
2226 : : /* Record the (approximate) median usage */
2227 [ # # ]: 0 : if (i > 0)
2228 : 0 : pgss->cur_median_usage = entries[i / 2]->counters.usage;
2229 : : /* Record the mean query length */
2230 [ # # ]: 0 : if (nvalidtexts > 0)
2231 : 0 : pgss->mean_query_len = tottextlen / nvalidtexts;
2232 : : else
2233 : 0 : pgss->mean_query_len = ASSUMED_LENGTH_INIT;
2234 : :
2235 : : /* Now zap an appropriate fraction of lowest-usage entries */
2236 [ # # ]: 0 : nvictims = Max(10, i * USAGE_DEALLOC_PERCENT / 100);
2237 : 0 : nvictims = Min(nvictims, i);
2238 : :
2239 [ # # ]: 0 : for (i = 0; i < nvictims; i++)
2240 : : {
2241 : 0 : hash_search(pgss_hash, &entries[i]->key, HASH_REMOVE, NULL);
2242 : : }
2243 : :
2244 : 0 : pfree(entries);
2245 : :
2246 : : /* Increment the number of times entries are deallocated */
2247 : 0 : SpinLockAcquire(&pgss->mutex);
2248 : 0 : pgss->stats.dealloc += 1;
2249 : 0 : SpinLockRelease(&pgss->mutex);
2250 : 0 : }
2251 : :
2252 : : /*
2253 : : * Given a query string (not necessarily null-terminated), allocate a new
2254 : : * entry in the external query text file and store the string there.
2255 : : *
2256 : : * If successful, returns true, and stores the new entry's offset in the file
2257 : : * into *query_offset. Also, if gc_count isn't NULL, *gc_count is set to the
2258 : : * number of garbage collections that have occurred so far.
2259 : : *
2260 : : * On failure, returns false.
2261 : : *
2262 : : * At least a shared lock on pgss->lock must be held by the caller, so as
2263 : : * to prevent a concurrent garbage collection. Share-lock-holding callers
2264 : : * should pass a gc_count pointer to obtain the number of garbage collections,
2265 : : * so that they can recheck the count after obtaining exclusive lock to
2266 : : * detect whether a garbage collection occurred (and removed this entry).
2267 : : */
2268 : : static bool
2269 : 31346 : qtext_store(const char *query, int query_len,
2270 : : Size *query_offset, int *gc_count)
2271 : : {
2272 : : Size off;
2273 : : int fd;
2274 : :
2275 : : /*
2276 : : * We use a spinlock to protect extent/n_writers/gc_count, so that
2277 : : * multiple processes may execute this function concurrently.
2278 : : */
2279 : 31346 : SpinLockAcquire(&pgss->mutex);
2280 : 31346 : off = pgss->extent;
2281 : 31346 : pgss->extent += query_len + 1;
2282 : 31346 : pgss->n_writers++;
2283 [ + - ]: 31346 : if (gc_count)
2284 : 31346 : *gc_count = pgss->gc_count;
2285 : 31346 : SpinLockRelease(&pgss->mutex);
2286 : :
2287 : 31346 : *query_offset = off;
2288 : :
2289 : : /*
2290 : : * Don't allow the file to grow larger than what qtext_load_file can
2291 : : * (theoretically) handle. This has been seen to be reachable on 32-bit
2292 : : * platforms.
2293 : : */
2294 [ - + ]: 31346 : if (unlikely(query_len >= MaxAllocHugeSize - off))
2295 : : {
2296 : 0 : errno = EFBIG; /* not quite right, but it'll do */
2297 : 0 : fd = -1;
2298 : 0 : goto error;
2299 : : }
2300 : :
2301 : : /* Now write the data into the successfully-reserved part of the file */
2302 : 31346 : fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDWR | O_CREAT | PG_BINARY);
2303 [ - + ]: 31346 : if (fd < 0)
2304 : 0 : goto error;
2305 : :
2306 [ - + ]: 31346 : if (pg_pwrite(fd, query, query_len, off) != query_len)
2307 : 0 : goto error;
2308 [ - + ]: 31346 : if (pg_pwrite(fd, "\0", 1, off + query_len) != 1)
2309 : 0 : goto error;
2310 : :
2311 : 31346 : CloseTransientFile(fd);
2312 : :
2313 : : /* Mark our write complete */
2314 : 31346 : SpinLockAcquire(&pgss->mutex);
2315 : 31346 : pgss->n_writers--;
2316 : 31346 : SpinLockRelease(&pgss->mutex);
2317 : :
2318 : 31346 : return true;
2319 : :
2320 : 0 : error:
2321 [ # # ]: 0 : ereport(LOG,
2322 : : (errcode_for_file_access(),
2323 : : errmsg("could not write file \"%s\": %m",
2324 : : PGSS_TEXT_FILE)));
2325 : :
2326 [ # # ]: 0 : if (fd >= 0)
2327 : 0 : CloseTransientFile(fd);
2328 : :
2329 : : /* Mark our write complete */
2330 : 0 : SpinLockAcquire(&pgss->mutex);
2331 : 0 : pgss->n_writers--;
2332 : 0 : SpinLockRelease(&pgss->mutex);
2333 : :
2334 : 0 : return false;
2335 : : }
2336 : :
2337 : : /*
2338 : : * Read the external query text file into a palloc'd buffer.
2339 : : *
2340 : : * Returns NULL (without throwing an error) if unable to read, eg
2341 : : * file not there or insufficient memory.
2342 : : *
2343 : : * On success, the buffer size is also returned into *buffer_size.
2344 : : *
2345 : : * This can be called without any lock on pgss->lock, but in that case
2346 : : * the caller is responsible for verifying that the result is sane.
2347 : : */
2348 : : static char *
2349 : 162 : qtext_load_file(Size *buffer_size)
2350 : : {
2351 : : char *buf;
2352 : : int fd;
2353 : : struct stat stat;
2354 : : Size nread;
2355 : :
2356 : 162 : fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDONLY | PG_BINARY);
2357 [ - + ]: 162 : if (fd < 0)
2358 : : {
2359 [ # # ]: 0 : if (errno != ENOENT)
2360 [ # # ]: 0 : ereport(LOG,
2361 : : (errcode_for_file_access(),
2362 : : errmsg("could not read file \"%s\": %m",
2363 : : PGSS_TEXT_FILE)));
2364 : 0 : return NULL;
2365 : : }
2366 : :
2367 : : /* Get file length */
2368 [ - + ]: 162 : if (fstat(fd, &stat))
2369 : : {
2370 [ # # ]: 0 : ereport(LOG,
2371 : : (errcode_for_file_access(),
2372 : : errmsg("could not stat file \"%s\": %m",
2373 : : PGSS_TEXT_FILE)));
2374 : 0 : CloseTransientFile(fd);
2375 : 0 : return NULL;
2376 : : }
2377 : :
2378 : : /* Allocate buffer; beware that off_t might be wider than size_t */
2379 [ + - ]: 162 : if (stat.st_size <= MaxAllocHugeSize)
2380 : 162 : buf = (char *) palloc_extended(stat.st_size, MCXT_ALLOC_HUGE | MCXT_ALLOC_NO_OOM);
2381 : : else
2382 : 0 : buf = NULL;
2383 [ - + ]: 162 : if (buf == NULL)
2384 : : {
2385 [ # # ]: 0 : ereport(LOG,
2386 : : (errcode(ERRCODE_OUT_OF_MEMORY),
2387 : : errmsg("out of memory"),
2388 : : errdetail("Could not allocate enough memory to read file \"%s\".",
2389 : : PGSS_TEXT_FILE)));
2390 : 0 : CloseTransientFile(fd);
2391 : 0 : return NULL;
2392 : : }
2393 : :
2394 : : /*
2395 : : * OK, slurp in the file. Windows fails if we try to read more than
2396 : : * INT_MAX bytes at once, and other platforms might not like that either,
2397 : : * so read a very large file in 1GB segments.
2398 : : */
2399 : 162 : nread = 0;
2400 [ + + ]: 323 : while (nread < stat.st_size)
2401 : : {
2402 : 161 : int toread = Min(1024 * 1024 * 1024, stat.st_size - nread);
2403 : :
2404 : : /*
2405 : : * If we get a short read and errno doesn't get set, the reason is
2406 : : * probably that garbage collection truncated the file since we did
2407 : : * the fstat(), so we don't log a complaint --- but we don't return
2408 : : * the data, either, since it's most likely corrupt due to concurrent
2409 : : * writes from garbage collection.
2410 : : */
2411 : 161 : errno = 0;
2412 [ - + ]: 161 : if (read(fd, buf + nread, toread) != toread)
2413 : : {
2414 [ # # ]: 0 : if (errno)
2415 [ # # ]: 0 : ereport(LOG,
2416 : : (errcode_for_file_access(),
2417 : : errmsg("could not read file \"%s\": %m",
2418 : : PGSS_TEXT_FILE)));
2419 : 0 : pfree(buf);
2420 : 0 : CloseTransientFile(fd);
2421 : 0 : return NULL;
2422 : : }
2423 : 161 : nread += toread;
2424 : : }
2425 : :
2426 [ - + ]: 162 : if (CloseTransientFile(fd) != 0)
2427 [ # # ]: 0 : ereport(LOG,
2428 : : (errcode_for_file_access(),
2429 : : errmsg("could not close file \"%s\": %m", PGSS_TEXT_FILE)));
2430 : :
2431 : 162 : *buffer_size = nread;
2432 : 162 : return buf;
2433 : : }
2434 : :
2435 : : /*
2436 : : * Locate a query text in the file image previously read by qtext_load_file().
2437 : : *
2438 : : * We validate the given offset/length, and return NULL if bogus. Otherwise,
2439 : : * the result points to a null-terminated string within the buffer.
2440 : : */
2441 : : static char *
2442 : 88297 : qtext_fetch(Size query_offset, int query_len,
2443 : : char *buffer, Size buffer_size)
2444 : : {
2445 : : /* File read failed? */
2446 [ - + ]: 88297 : if (buffer == NULL)
2447 : 0 : return NULL;
2448 : : /* Bogus offset/length? */
2449 [ + - ]: 88297 : if (query_len < 0 ||
2450 [ - + ]: 88297 : query_offset + query_len >= buffer_size)
2451 : 0 : return NULL;
2452 : : /* As a further sanity check, make sure there's a trailing null */
2453 [ - + ]: 88297 : if (buffer[query_offset + query_len] != '\0')
2454 : 0 : return NULL;
2455 : : /* Looks OK */
2456 : 88297 : return buffer + query_offset;
2457 : : }
2458 : :
2459 : : /*
2460 : : * Do we need to garbage-collect the external query text file?
2461 : : *
2462 : : * Caller should hold at least a shared lock on pgss->lock.
2463 : : */
2464 : : static bool
2465 : 31346 : need_gc_qtexts(void)
2466 : : {
2467 : : Size extent;
2468 : :
2469 : : /* Read shared extent pointer */
2470 : 31346 : SpinLockAcquire(&pgss->mutex);
2471 : 31346 : extent = pgss->extent;
2472 : 31346 : SpinLockRelease(&pgss->mutex);
2473 : :
2474 : : /*
2475 : : * Don't proceed if file does not exceed 512 bytes per possible entry.
2476 : : *
2477 : : * Here and in the next test, 32-bit machines have overflow hazards if
2478 : : * pgss_max and/or mean_query_len are large. Force the multiplications
2479 : : * and comparisons to be done in uint64 arithmetic to forestall trouble.
2480 : : */
2481 [ + - ]: 31346 : if ((uint64) extent < (uint64) 512 * pgss_max)
2482 : 31346 : return false;
2483 : :
2484 : : /*
2485 : : * Don't proceed if file is less than about 50% bloat. Nothing can or
2486 : : * should be done in the event of unusually large query texts accounting
2487 : : * for file's large size. We go to the trouble of maintaining the mean
2488 : : * query length in order to prevent garbage collection from thrashing
2489 : : * uselessly.
2490 : : */
2491 [ # # ]: 0 : if ((uint64) extent < (uint64) pgss->mean_query_len * pgss_max * 2)
2492 : 0 : return false;
2493 : :
2494 : 0 : return true;
2495 : : }
2496 : :
2497 : : /*
2498 : : * Garbage-collect orphaned query texts in external file.
2499 : : *
2500 : : * This won't be called often in the typical case, since it's likely that
2501 : : * there won't be too much churn, and besides, a similar compaction process
2502 : : * occurs when serializing to disk at shutdown or as part of resetting.
2503 : : * Despite this, it seems prudent to plan for the edge case where the file
2504 : : * becomes unreasonably large, with no other method of compaction likely to
2505 : : * occur in the foreseeable future.
2506 : : *
2507 : : * The caller must hold an exclusive lock on pgss->lock.
2508 : : *
2509 : : * At the first sign of trouble we unlink the query text file to get a clean
2510 : : * slate (although existing statistics are retained), rather than risk
2511 : : * thrashing by allowing the same problem case to recur indefinitely.
2512 : : */
2513 : : static void
2514 : 0 : gc_qtexts(void)
2515 : : {
2516 : : char *qbuffer;
2517 : : Size qbuffer_size;
2518 : 0 : FILE *qfile = NULL;
2519 : : HASH_SEQ_STATUS hash_seq;
2520 : : pgssEntry *entry;
2521 : : Size extent;
2522 : : int nentries;
2523 : :
2524 : : /*
2525 : : * When called from pgss_store, some other session might have proceeded
2526 : : * with garbage collection in the no-lock-held interim of lock strength
2527 : : * escalation. Check once more that this is actually necessary.
2528 : : */
2529 [ # # ]: 0 : if (!need_gc_qtexts())
2530 : 0 : return;
2531 : :
2532 : : /*
2533 : : * Load the old texts file. If we fail (out of memory, for instance),
2534 : : * invalidate query texts. Hopefully this is rare. It might seem better
2535 : : * to leave things alone on an OOM failure, but the problem is that the
2536 : : * file is only going to get bigger; hoping for a future non-OOM result is
2537 : : * risky and can easily lead to complete denial of service.
2538 : : */
2539 : 0 : qbuffer = qtext_load_file(&qbuffer_size);
2540 [ # # ]: 0 : if (qbuffer == NULL)
2541 : 0 : goto gc_fail;
2542 : :
2543 : : /*
2544 : : * We overwrite the query texts file in place, so as to reduce the risk of
2545 : : * an out-of-disk-space failure. Since the file is guaranteed not to get
2546 : : * larger, this should always work on traditional filesystems; though we
2547 : : * could still lose on copy-on-write filesystems.
2548 : : */
2549 : 0 : qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
2550 [ # # ]: 0 : if (qfile == NULL)
2551 : : {
2552 [ # # ]: 0 : ereport(LOG,
2553 : : (errcode_for_file_access(),
2554 : : errmsg("could not write file \"%s\": %m",
2555 : : PGSS_TEXT_FILE)));
2556 : 0 : goto gc_fail;
2557 : : }
2558 : :
2559 : 0 : extent = 0;
2560 : 0 : nentries = 0;
2561 : :
2562 : 0 : hash_seq_init(&hash_seq, pgss_hash);
2563 [ # # ]: 0 : while ((entry = hash_seq_search(&hash_seq)) != NULL)
2564 : : {
2565 : 0 : int query_len = entry->query_len;
2566 : 0 : char *qry = qtext_fetch(entry->query_offset,
2567 : : query_len,
2568 : : qbuffer,
2569 : : qbuffer_size);
2570 : :
2571 [ # # ]: 0 : if (qry == NULL)
2572 : : {
2573 : : /* Trouble ... drop the text */
2574 : 0 : entry->query_offset = 0;
2575 : 0 : entry->query_len = -1;
2576 : : /* entry will not be counted in mean query length computation */
2577 : 0 : continue;
2578 : : }
2579 : :
2580 [ # # ]: 0 : if (fwrite(qry, 1, query_len + 1, qfile) != query_len + 1)
2581 : : {
2582 [ # # ]: 0 : ereport(LOG,
2583 : : (errcode_for_file_access(),
2584 : : errmsg("could not write file \"%s\": %m",
2585 : : PGSS_TEXT_FILE)));
2586 : 0 : hash_seq_term(&hash_seq);
2587 : 0 : goto gc_fail;
2588 : : }
2589 : :
2590 : 0 : entry->query_offset = extent;
2591 : 0 : extent += query_len + 1;
2592 : 0 : nentries++;
2593 : : }
2594 : :
2595 : : /*
2596 : : * Truncate away any now-unused space. If this fails for some odd reason,
2597 : : * we log it, but there's no need to fail.
2598 : : */
2599 [ # # ]: 0 : if (ftruncate(fileno(qfile), extent) != 0)
2600 [ # # ]: 0 : ereport(LOG,
2601 : : (errcode_for_file_access(),
2602 : : errmsg("could not truncate file \"%s\": %m",
2603 : : PGSS_TEXT_FILE)));
2604 : :
2605 [ # # ]: 0 : if (FreeFile(qfile))
2606 : : {
2607 [ # # ]: 0 : ereport(LOG,
2608 : : (errcode_for_file_access(),
2609 : : errmsg("could not write file \"%s\": %m",
2610 : : PGSS_TEXT_FILE)));
2611 : 0 : qfile = NULL;
2612 : 0 : goto gc_fail;
2613 : : }
2614 : :
2615 [ # # ]: 0 : elog(DEBUG1, "pgss gc of queries file shrunk size from %zu to %zu",
2616 : : pgss->extent, extent);
2617 : :
2618 : : /* Reset the shared extent pointer */
2619 : 0 : pgss->extent = extent;
2620 : :
2621 : : /*
2622 : : * Also update the mean query length, to be sure that need_gc_qtexts()
2623 : : * won't still think we have a problem.
2624 : : */
2625 [ # # ]: 0 : if (nentries > 0)
2626 : 0 : pgss->mean_query_len = extent / nentries;
2627 : : else
2628 : 0 : pgss->mean_query_len = ASSUMED_LENGTH_INIT;
2629 : :
2630 : 0 : pfree(qbuffer);
2631 : :
2632 : : /*
2633 : : * OK, count a garbage collection cycle. (Note: even though we have
2634 : : * exclusive lock on pgss->lock, we must take pgss->mutex for this, since
2635 : : * other processes may examine gc_count while holding only the mutex.
2636 : : * Also, we have to advance the count *after* we've rewritten the file,
2637 : : * else other processes might not realize they read a stale file.)
2638 : : */
2639 : 0 : record_gc_qtexts();
2640 : :
2641 : 0 : return;
2642 : :
2643 : 0 : gc_fail:
2644 : : /* clean up resources */
2645 [ # # ]: 0 : if (qfile)
2646 : 0 : FreeFile(qfile);
2647 [ # # ]: 0 : if (qbuffer)
2648 : 0 : pfree(qbuffer);
2649 : :
2650 : : /*
2651 : : * Since the contents of the external file are now uncertain, mark all
2652 : : * hashtable entries as having invalid texts.
2653 : : */
2654 : 0 : hash_seq_init(&hash_seq, pgss_hash);
2655 [ # # ]: 0 : while ((entry = hash_seq_search(&hash_seq)) != NULL)
2656 : : {
2657 : 0 : entry->query_offset = 0;
2658 : 0 : entry->query_len = -1;
2659 : : }
2660 : :
2661 : : /*
2662 : : * Destroy the query text file and create a new, empty one
2663 : : */
2664 : 0 : (void) unlink(PGSS_TEXT_FILE);
2665 : 0 : qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
2666 [ # # ]: 0 : if (qfile == NULL)
2667 [ # # ]: 0 : ereport(LOG,
2668 : : (errcode_for_file_access(),
2669 : : errmsg("could not recreate file \"%s\": %m",
2670 : : PGSS_TEXT_FILE)));
2671 : : else
2672 : 0 : FreeFile(qfile);
2673 : :
2674 : : /* Reset the shared extent pointer */
2675 : 0 : pgss->extent = 0;
2676 : :
2677 : : /* Reset mean_query_len to match the new state */
2678 : 0 : pgss->mean_query_len = ASSUMED_LENGTH_INIT;
2679 : :
2680 : : /*
2681 : : * Bump the GC count even though we failed.
2682 : : *
2683 : : * This is needed to make concurrent readers of file without any lock on
2684 : : * pgss->lock notice existence of new version of file. Once readers
2685 : : * subsequently observe a change in GC count with pgss->lock held, that
2686 : : * forces a safe reopen of file. Writers also require that we bump here,
2687 : : * of course. (As required by locking protocol, readers and writers don't
2688 : : * trust earlier file contents until gc_count is found unchanged after
2689 : : * pgss->lock acquired in shared or exclusive mode respectively.)
2690 : : */
2691 : 0 : record_gc_qtexts();
2692 : : }
2693 : :
2694 : : #define SINGLE_ENTRY_RESET(e) \
2695 : : if (e) { \
2696 : : if (minmax_only) { \
2697 : : /* When requested reset only min/max statistics of an entry */ \
2698 : : for (int kind = 0; kind < PGSS_NUMKIND; kind++) \
2699 : : { \
2700 : : e->counters.max_time[kind] = 0; \
2701 : : e->counters.min_time[kind] = 0; \
2702 : : } \
2703 : : e->minmax_stats_since = stats_reset; \
2704 : : } \
2705 : : else \
2706 : : { \
2707 : : /* Remove the key otherwise */ \
2708 : : hash_search(pgss_hash, &e->key, HASH_REMOVE, NULL); \
2709 : : num_remove++; \
2710 : : } \
2711 : : }
2712 : :
2713 : : /*
2714 : : * Reset entries corresponding to parameters passed.
2715 : : */
2716 : : static TimestampTz
2717 : 141 : entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
2718 : : {
2719 : : HASH_SEQ_STATUS hash_seq;
2720 : : pgssEntry *entry;
2721 : : FILE *qfile;
2722 : : int64 num_entries;
2723 : 141 : int64 num_remove = 0;
2724 : : pgssHashKey key;
2725 : : TimestampTz stats_reset;
2726 : :
2727 [ + - - + ]: 141 : if (!pgss || !pgss_hash)
2728 [ # # ]: 0 : ereport(ERROR,
2729 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
2730 : : errmsg("pg_stat_statements must be loaded via \"shared_preload_libraries\"")));
2731 : :
2732 : 141 : LWLockAcquire(&pgss->lock.lock, LW_EXCLUSIVE);
2733 : 141 : num_entries = hash_get_num_entries(pgss_hash);
2734 : :
2735 : 141 : stats_reset = GetCurrentTimestamp();
2736 : :
2737 [ + + + + : 141 : if (userid != 0 && dbid != 0 && queryid != INT64CONST(0))
+ - ]
2738 : : {
2739 : : /* If all the parameters are available, use the fast path. */
2740 : 1 : memset(&key, 0, sizeof(pgssHashKey));
2741 : 1 : key.userid = userid;
2742 : 1 : key.dbid = dbid;
2743 : 1 : key.queryid = queryid;
2744 : :
2745 : : /*
2746 : : * Reset the entry if it exists, starting with the non-top-level
2747 : : * entry.
2748 : : */
2749 : 1 : key.toplevel = false;
2750 : 1 : entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL);
2751 : :
2752 [ - + - - : 1 : SINGLE_ENTRY_RESET(entry);
- - ]
2753 : :
2754 : : /* Also reset the top-level entry if it exists. */
2755 : 1 : key.toplevel = true;
2756 : 1 : entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL);
2757 : :
2758 [ + - - + : 1 : SINGLE_ENTRY_RESET(entry);
- - ]
2759 : : }
2760 [ + + + - : 140 : else if (userid != 0 || dbid != 0 || queryid != INT64CONST(0))
+ + ]
2761 : : {
2762 : : /* Reset entries corresponding to valid parameters. */
2763 : 4 : hash_seq_init(&hash_seq, pgss_hash);
2764 [ + + ]: 51 : while ((entry = hash_seq_search(&hash_seq)) != NULL)
2765 : : {
2766 [ + + + + : 47 : if ((!userid || entry->key.userid == userid) &&
- + ]
2767 [ - - + + ]: 36 : (!dbid || entry->key.dbid == dbid) &&
2768 [ + + ]: 34 : (!queryid || entry->key.queryid == queryid))
2769 : : {
2770 [ + - + + : 7 : SINGLE_ENTRY_RESET(entry);
+ + ]
2771 : : }
2772 : : }
2773 : : }
2774 : : else
2775 : : {
2776 : : /* Reset all entries. */
2777 : 136 : hash_seq_init(&hash_seq, pgss_hash);
2778 [ + + ]: 1343 : while ((entry = hash_seq_search(&hash_seq)) != NULL)
2779 : : {
2780 [ + - + + : 1093 : SINGLE_ENTRY_RESET(entry);
+ + ]
2781 : : }
2782 : : }
2783 : :
2784 : : /* All entries are removed? */
2785 [ + + ]: 141 : if (num_entries != num_remove)
2786 : 6 : goto release_lock;
2787 : :
2788 : : /*
2789 : : * Reset global statistics for pg_stat_statements since all entries are
2790 : : * removed.
2791 : : */
2792 : 135 : SpinLockAcquire(&pgss->mutex);
2793 : 135 : pgss->stats.dealloc = 0;
2794 : 135 : pgss->stats.stats_reset = stats_reset;
2795 : 135 : SpinLockRelease(&pgss->mutex);
2796 : :
2797 : : /*
2798 : : * Write new empty query file, perhaps even creating a new one to recover
2799 : : * if the file was missing.
2800 : : */
2801 : 135 : qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
2802 [ - + ]: 135 : if (qfile == NULL)
2803 : : {
2804 [ # # ]: 0 : ereport(LOG,
2805 : : (errcode_for_file_access(),
2806 : : errmsg("could not create file \"%s\": %m",
2807 : : PGSS_TEXT_FILE)));
2808 : 0 : goto done;
2809 : : }
2810 : :
2811 : : /* If ftruncate fails, log it, but it's not a fatal problem */
2812 [ - + ]: 135 : if (ftruncate(fileno(qfile), 0) != 0)
2813 [ # # ]: 0 : ereport(LOG,
2814 : : (errcode_for_file_access(),
2815 : : errmsg("could not truncate file \"%s\": %m",
2816 : : PGSS_TEXT_FILE)));
2817 : :
2818 : 135 : FreeFile(qfile);
2819 : :
2820 : 135 : done:
2821 : 135 : pgss->extent = 0;
2822 : : /* This counts as a query text garbage collection for our purposes */
2823 : 135 : record_gc_qtexts();
2824 : :
2825 : 141 : release_lock:
2826 : 141 : LWLockRelease(&pgss->lock.lock);
2827 : :
2828 : 141 : return stats_reset;
2829 : : }
2830 : :
2831 : : /*
2832 : : * Generate a normalized version of the query string that will be used to
2833 : : * represent all similar queries.
2834 : : *
2835 : : * Note that the normalized representation may well vary depending on
2836 : : * just which "equivalent" query is used to create the hashtable entry.
2837 : : * We assume this is OK.
2838 : : *
2839 : : * If query_loc > 0, then "query" has been advanced by that much compared to
2840 : : * the original string start, so we need to translate the provided locations
2841 : : * to compensate. (This lets us avoid re-scanning statements before the one
2842 : : * of interest, so it's worth doing.)
2843 : : *
2844 : : * *query_len_p contains the input string length, and is updated with
2845 : : * the result string length on exit. The resulting string might be longer
2846 : : * or shorter depending on what happens with replacement of constants.
2847 : : *
2848 : : * Returns a palloc'd string.
2849 : : */
2850 : : static char *
2851 : 11857 : generate_normalized_query(const JumbleState *jstate, const char *query,
2852 : : int query_loc, int *query_len_p)
2853 : : {
2854 : : StringInfoData norm_query;
2855 : 11857 : int query_len = *query_len_p;
2856 : : int len_to_wrt, /* Length (in bytes) to write */
2857 : 11857 : quer_loc = 0, /* Source query byte location */
2858 : 11857 : last_off = 0, /* Offset from start for previous tok */
2859 : 11857 : last_tok_len = 0; /* Length (in bytes) of that tok */
2860 : 11857 : int num_constants_replaced = 0;
2861 : 11857 : LocationLen *locs = NULL;
2862 : :
2863 : : /*
2864 : : * Our output buffer is an expansible StringInfo, but avoid enlarging it
2865 : : * in most cases by reserving extra space for each constant location.
2866 : : */
2867 : : Assert(jstate->clocations_count > 0);
2868 : 11857 : initStringInfoExt(&norm_query, query_len + jstate->clocations_count * 10);
2869 : :
2870 : : /*
2871 : : * Determine constants' lengths (core system only gives us locations), and
2872 : : * return a sorted copy of jstate's LocationLen data with lengths filled
2873 : : * in.
2874 : : */
2875 : 11857 : locs = ComputeConstantLengths(jstate, query, query_loc);
2876 : :
2877 [ + + ]: 46628 : for (int i = 0; i < jstate->clocations_count; i++)
2878 : : {
2879 : : int off, /* Offset from start for cur tok */
2880 : : tok_len; /* Length (in bytes) of that tok */
2881 : :
2882 : : /*
2883 : : * If we have an external param at this location, but no lists are
2884 : : * being squashed across the query, then we skip here; this will make
2885 : : * us print the characters found in the original query that represent
2886 : : * the parameter in the next iteration (or after the loop is done),
2887 : : * which is a bit odd but seems to work okay in most cases.
2888 : : */
2889 [ + + + + ]: 34771 : if (locs[i].extern_param && !jstate->has_squashed_lists)
2890 : 163 : continue;
2891 : :
2892 : 34608 : off = locs[i].location;
2893 : :
2894 : : /* Adjust recorded location if we're dealing with partial string */
2895 : 34608 : off -= query_loc;
2896 : :
2897 : 34608 : tok_len = locs[i].length;
2898 : :
2899 [ + + ]: 34608 : if (tok_len < 0)
2900 : 316 : continue; /* ignore any duplicates */
2901 : :
2902 : : /* Copy next chunk (what precedes the next constant) */
2903 : 34292 : len_to_wrt = off - last_off;
2904 : 34292 : len_to_wrt -= last_tok_len;
2905 : : Assert(len_to_wrt >= 0);
2906 : 34292 : appendBinaryStringInfo(&norm_query, query + quer_loc, len_to_wrt);
2907 : :
2908 : : /*
2909 : : * And insert a param symbol in place of the constant token; and, if
2910 : : * we have a squashable list, insert a placeholder comment starting
2911 : : * from the list's second value.
2912 : : */
2913 : 34292 : appendStringInfo(&norm_query, "$%d%s",
2914 : 34292 : num_constants_replaced + 1 + jstate->highest_extern_param_id,
2915 [ + + ]: 34292 : locs[i].squashed ? " /*, ... */" : "");
2916 : 34292 : num_constants_replaced++;
2917 : :
2918 : : /* move forward */
2919 : 34292 : quer_loc = off + tok_len;
2920 : 34292 : last_off = off;
2921 : 34292 : last_tok_len = tok_len;
2922 : : }
2923 : :
2924 : : /* Clean up, if needed */
2925 [ + - ]: 11857 : if (locs)
2926 : 11857 : pfree(locs);
2927 : :
2928 : : /*
2929 : : * We've copied up until the last ignorable constant. Copy over the
2930 : : * remaining bytes of the original query string.
2931 : : */
2932 : 11857 : len_to_wrt = query_len - quer_loc;
2933 : :
2934 : : Assert(len_to_wrt >= 0);
2935 : 11857 : appendBinaryStringInfo(&norm_query, query + quer_loc, len_to_wrt);
2936 : :
2937 : 11857 : *query_len_p = norm_query.len;
2938 : 11857 : return norm_query.data;
2939 : : }
|