1 /*-------------------------------------------------------------------------
6 * Copyright (c) 2008-2025, PostgreSQL Global Development Group
9 * contrib/auto_explain/auto_explain.c
11 *-------------------------------------------------------------------------
17 #include "access/parallel.h"
18 #include "commands/explain.h"
19 #include "common/pg_prng.h"
20 #include "executor/instrument.h"
21 #include "utils/guc.h"
26 static int auto_explain_log_min_duration
= -1; /* msec or -1 */
27 static int auto_explain_log_parameter_max_length
= -1; /* bytes or -1 */
28 static bool auto_explain_log_analyze
= false;
29 static bool auto_explain_log_verbose
= false;
30 static bool auto_explain_log_buffers
= false;
31 static bool auto_explain_log_wal
= false;
32 static bool auto_explain_log_triggers
= false;
33 static bool auto_explain_log_timing
= true;
34 static bool auto_explain_log_settings
= false;
35 static int auto_explain_log_format
= EXPLAIN_FORMAT_TEXT
;
36 static int auto_explain_log_level
= LOG
;
37 static bool auto_explain_log_nested_statements
= false;
38 static double auto_explain_sample_rate
= 1;
40 static const struct config_enum_entry format_options
[] = {
41 {"text", EXPLAIN_FORMAT_TEXT
, false},
42 {"xml", EXPLAIN_FORMAT_XML
, false},
43 {"json", EXPLAIN_FORMAT_JSON
, false},
44 {"yaml", EXPLAIN_FORMAT_YAML
, false},
48 static const struct config_enum_entry loglevel_options
[] = {
49 {"debug5", DEBUG5
, false},
50 {"debug4", DEBUG4
, false},
51 {"debug3", DEBUG3
, false},
52 {"debug2", DEBUG2
, false},
53 {"debug1", DEBUG1
, false},
54 {"debug", DEBUG2
, true},
55 {"info", INFO
, false},
56 {"notice", NOTICE
, false},
57 {"warning", WARNING
, false},
62 /* Current nesting depth of ExecutorRun calls */
63 static int nesting_level
= 0;
65 /* Is the current top-level query to be sampled? */
66 static bool current_query_sampled
= false;
68 #define auto_explain_enabled() \
69 (auto_explain_log_min_duration >= 0 && \
70 (nesting_level == 0 || auto_explain_log_nested_statements) && \
71 current_query_sampled)
73 /* Saved hook values */
74 static ExecutorStart_hook_type prev_ExecutorStart
= NULL
;
75 static ExecutorRun_hook_type prev_ExecutorRun
= NULL
;
76 static ExecutorFinish_hook_type prev_ExecutorFinish
= NULL
;
77 static ExecutorEnd_hook_type prev_ExecutorEnd
= NULL
;
79 static void explain_ExecutorStart(QueryDesc
*queryDesc
, int eflags
);
80 static void explain_ExecutorRun(QueryDesc
*queryDesc
,
81 ScanDirection direction
,
83 static void explain_ExecutorFinish(QueryDesc
*queryDesc
);
84 static void explain_ExecutorEnd(QueryDesc
*queryDesc
);
88 * Module load callback
93 /* Define custom GUC variables. */
94 DefineCustomIntVariable("auto_explain.log_min_duration",
95 "Sets the minimum execution time above which plans will be logged.",
96 "Zero prints all plans. -1 turns this feature off.",
97 &auto_explain_log_min_duration
,
106 DefineCustomIntVariable("auto_explain.log_parameter_max_length",
107 "Sets the maximum length of query parameters to log.",
108 "Zero logs no query parameters, -1 logs them in full.",
109 &auto_explain_log_parameter_max_length
,
118 DefineCustomBoolVariable("auto_explain.log_analyze",
119 "Use EXPLAIN ANALYZE for plan logging.",
121 &auto_explain_log_analyze
,
129 DefineCustomBoolVariable("auto_explain.log_settings",
130 "Log modified configuration parameters affecting query planning.",
132 &auto_explain_log_settings
,
140 DefineCustomBoolVariable("auto_explain.log_verbose",
141 "Use EXPLAIN VERBOSE for plan logging.",
143 &auto_explain_log_verbose
,
151 DefineCustomBoolVariable("auto_explain.log_buffers",
152 "Log buffers usage.",
154 &auto_explain_log_buffers
,
162 DefineCustomBoolVariable("auto_explain.log_wal",
165 &auto_explain_log_wal
,
173 DefineCustomBoolVariable("auto_explain.log_triggers",
174 "Include trigger statistics in plans.",
175 "This has no effect unless log_analyze is also set.",
176 &auto_explain_log_triggers
,
184 DefineCustomEnumVariable("auto_explain.log_format",
185 "EXPLAIN format to be used for plan logging.",
187 &auto_explain_log_format
,
196 DefineCustomEnumVariable("auto_explain.log_level",
197 "Log level for the plan.",
199 &auto_explain_log_level
,
208 DefineCustomBoolVariable("auto_explain.log_nested_statements",
209 "Log nested statements.",
211 &auto_explain_log_nested_statements
,
219 DefineCustomBoolVariable("auto_explain.log_timing",
220 "Collect timing data, not just row counts.",
222 &auto_explain_log_timing
,
230 DefineCustomRealVariable("auto_explain.sample_rate",
231 "Fraction of queries to process.",
233 &auto_explain_sample_rate
,
243 MarkGUCPrefixReserved("auto_explain");
246 prev_ExecutorStart
= ExecutorStart_hook
;
247 ExecutorStart_hook
= explain_ExecutorStart
;
248 prev_ExecutorRun
= ExecutorRun_hook
;
249 ExecutorRun_hook
= explain_ExecutorRun
;
250 prev_ExecutorFinish
= ExecutorFinish_hook
;
251 ExecutorFinish_hook
= explain_ExecutorFinish
;
252 prev_ExecutorEnd
= ExecutorEnd_hook
;
253 ExecutorEnd_hook
= explain_ExecutorEnd
;
257 * ExecutorStart hook: start up logging if needed
260 explain_ExecutorStart(QueryDesc
*queryDesc
, int eflags
)
263 * At the beginning of each top-level statement, decide whether we'll
264 * sample this statement. If nested-statement explaining is enabled,
265 * either all nested statements will be explained or none will.
267 * When in a parallel worker, we should do nothing, which we can implement
268 * cheaply by pretending we decided not to sample the current statement.
269 * If EXPLAIN is active in the parent session, data will be collected and
270 * reported back to the parent, and it's no business of ours to interfere.
272 if (nesting_level
== 0)
274 if (auto_explain_log_min_duration
>= 0 && !IsParallelWorker())
275 current_query_sampled
= (pg_prng_double(&pg_global_prng_state
) < auto_explain_sample_rate
);
277 current_query_sampled
= false;
280 if (auto_explain_enabled())
282 /* Enable per-node instrumentation iff log_analyze is required. */
283 if (auto_explain_log_analyze
&& (eflags
& EXEC_FLAG_EXPLAIN_ONLY
) == 0)
285 if (auto_explain_log_timing
)
286 queryDesc
->instrument_options
|= INSTRUMENT_TIMER
;
288 queryDesc
->instrument_options
|= INSTRUMENT_ROWS
;
289 if (auto_explain_log_buffers
)
290 queryDesc
->instrument_options
|= INSTRUMENT_BUFFERS
;
291 if (auto_explain_log_wal
)
292 queryDesc
->instrument_options
|= INSTRUMENT_WAL
;
296 if (prev_ExecutorStart
)
297 prev_ExecutorStart(queryDesc
, eflags
);
299 standard_ExecutorStart(queryDesc
, eflags
);
301 if (auto_explain_enabled())
304 * Set up to track total elapsed time in ExecutorRun. Make sure the
305 * space is allocated in the per-query context so it will go away at
308 if (queryDesc
->totaltime
== NULL
)
310 MemoryContext oldcxt
;
312 oldcxt
= MemoryContextSwitchTo(queryDesc
->estate
->es_query_cxt
);
313 queryDesc
->totaltime
= InstrAlloc(1, INSTRUMENT_ALL
, false);
314 MemoryContextSwitchTo(oldcxt
);
320 * ExecutorRun hook: all we need do is track nesting depth
323 explain_ExecutorRun(QueryDesc
*queryDesc
, ScanDirection direction
,
329 if (prev_ExecutorRun
)
330 prev_ExecutorRun(queryDesc
, direction
, count
);
332 standard_ExecutorRun(queryDesc
, direction
, count
);
342 * ExecutorFinish hook: all we need do is track nesting depth
345 explain_ExecutorFinish(QueryDesc
*queryDesc
)
350 if (prev_ExecutorFinish
)
351 prev_ExecutorFinish(queryDesc
);
353 standard_ExecutorFinish(queryDesc
);
363 * ExecutorEnd hook: log results if needed
366 explain_ExecutorEnd(QueryDesc
*queryDesc
)
368 if (queryDesc
->totaltime
&& auto_explain_enabled())
370 MemoryContext oldcxt
;
374 * Make sure we operate in the per-query context, so any cruft will be
375 * discarded later during ExecutorEnd.
377 oldcxt
= MemoryContextSwitchTo(queryDesc
->estate
->es_query_cxt
);
380 * Make sure stats accumulation is done. (Note: it's okay if several
381 * levels of hook all do this.)
383 InstrEndLoop(queryDesc
->totaltime
);
385 /* Log plan if duration is exceeded. */
386 msec
= queryDesc
->totaltime
->total
* 1000.0;
387 if (msec
>= auto_explain_log_min_duration
)
389 ExplainState
*es
= NewExplainState();
391 es
->analyze
= (queryDesc
->instrument_options
&& auto_explain_log_analyze
);
392 es
->verbose
= auto_explain_log_verbose
;
393 es
->buffers
= (es
->analyze
&& auto_explain_log_buffers
);
394 es
->wal
= (es
->analyze
&& auto_explain_log_wal
);
395 es
->timing
= (es
->analyze
&& auto_explain_log_timing
);
396 es
->summary
= es
->analyze
;
397 /* No support for MEMORY */
398 /* es->memory = false; */
399 es
->format
= auto_explain_log_format
;
400 es
->settings
= auto_explain_log_settings
;
402 ExplainBeginOutput(es
);
403 ExplainQueryText(es
, queryDesc
);
404 ExplainQueryParameters(es
, queryDesc
->params
, auto_explain_log_parameter_max_length
);
405 ExplainPrintPlan(es
, queryDesc
);
406 if (es
->analyze
&& auto_explain_log_triggers
)
407 ExplainPrintTriggers(es
, queryDesc
);
409 ExplainPrintJITSummary(es
, queryDesc
);
410 ExplainEndOutput(es
);
412 /* Remove last line break */
413 if (es
->str
->len
> 0 && es
->str
->data
[es
->str
->len
- 1] == '\n')
414 es
->str
->data
[--es
->str
->len
] = '\0';
416 /* Fix JSON to output an object */
417 if (auto_explain_log_format
== EXPLAIN_FORMAT_JSON
)
419 es
->str
->data
[0] = '{';
420 es
->str
->data
[es
->str
->len
- 1] = '}';
424 * Note: we rely on the existing logging of context or
425 * debug_query_string to identify just which statement is being
426 * reported. This isn't ideal but trying to do it here would
427 * often result in duplication.
429 ereport(auto_explain_log_level
,
430 (errmsg("duration: %.3f ms plan:\n%s",
431 msec
, es
->str
->data
),
435 MemoryContextSwitchTo(oldcxt
);
438 if (prev_ExecutorEnd
)
439 prev_ExecutorEnd(queryDesc
);
441 standard_ExecutorEnd(queryDesc
);