1 /*-------------------------------------------------------------------------
6 * Copyright (c) 2008-2024, 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"
22 #include "nodes/params.h"
23 #include "utils/guc.h"
28 static int auto_explain_log_min_duration
= -1; /* msec or -1 */
29 static int auto_explain_log_parameter_max_length
= -1; /* bytes or -1 */
30 static bool auto_explain_log_analyze
= false;
31 static bool auto_explain_log_verbose
= false;
32 static bool auto_explain_log_buffers
= false;
33 static bool auto_explain_log_wal
= false;
34 static bool auto_explain_log_triggers
= false;
35 static bool auto_explain_log_timing
= true;
36 static bool auto_explain_log_settings
= false;
37 static int auto_explain_log_format
= EXPLAIN_FORMAT_TEXT
;
38 static int auto_explain_log_level
= LOG
;
39 static bool auto_explain_log_nested_statements
= false;
40 static double auto_explain_sample_rate
= 1;
42 static const struct config_enum_entry format_options
[] = {
43 {"text", EXPLAIN_FORMAT_TEXT
, false},
44 {"xml", EXPLAIN_FORMAT_XML
, false},
45 {"json", EXPLAIN_FORMAT_JSON
, false},
46 {"yaml", EXPLAIN_FORMAT_YAML
, false},
50 static const struct config_enum_entry loglevel_options
[] = {
51 {"debug5", DEBUG5
, false},
52 {"debug4", DEBUG4
, false},
53 {"debug3", DEBUG3
, false},
54 {"debug2", DEBUG2
, false},
55 {"debug1", DEBUG1
, false},
56 {"debug", DEBUG2
, true},
57 {"info", INFO
, false},
58 {"notice", NOTICE
, false},
59 {"warning", WARNING
, false},
64 /* Current nesting depth of ExecutorRun calls */
65 static int nesting_level
= 0;
67 /* Is the current top-level query to be sampled? */
68 static bool current_query_sampled
= false;
70 #define auto_explain_enabled() \
71 (auto_explain_log_min_duration >= 0 && \
72 (nesting_level == 0 || auto_explain_log_nested_statements) && \
73 current_query_sampled)
75 /* Saved hook values in case of unload */
76 static ExecutorStart_hook_type prev_ExecutorStart
= NULL
;
77 static ExecutorRun_hook_type prev_ExecutorRun
= NULL
;
78 static ExecutorFinish_hook_type prev_ExecutorFinish
= NULL
;
79 static ExecutorEnd_hook_type prev_ExecutorEnd
= NULL
;
81 static void explain_ExecutorStart(QueryDesc
*queryDesc
, int eflags
);
82 static void explain_ExecutorRun(QueryDesc
*queryDesc
,
83 ScanDirection direction
,
84 uint64 count
, bool execute_once
);
85 static void explain_ExecutorFinish(QueryDesc
*queryDesc
);
86 static void explain_ExecutorEnd(QueryDesc
*queryDesc
);
90 * Module load callback
95 /* Define custom GUC variables. */
96 DefineCustomIntVariable("auto_explain.log_min_duration",
97 "Sets the minimum execution time above which plans will be logged.",
98 "Zero prints all plans. -1 turns this feature off.",
99 &auto_explain_log_min_duration
,
108 DefineCustomIntVariable("auto_explain.log_parameter_max_length",
109 "Sets the maximum length of query parameters to log.",
110 "Zero logs no query parameters, -1 logs them in full.",
111 &auto_explain_log_parameter_max_length
,
120 DefineCustomBoolVariable("auto_explain.log_analyze",
121 "Use EXPLAIN ANALYZE for plan logging.",
123 &auto_explain_log_analyze
,
131 DefineCustomBoolVariable("auto_explain.log_settings",
132 "Log modified configuration parameters affecting query planning.",
134 &auto_explain_log_settings
,
142 DefineCustomBoolVariable("auto_explain.log_verbose",
143 "Use EXPLAIN VERBOSE for plan logging.",
145 &auto_explain_log_verbose
,
153 DefineCustomBoolVariable("auto_explain.log_buffers",
154 "Log buffers usage.",
156 &auto_explain_log_buffers
,
164 DefineCustomBoolVariable("auto_explain.log_wal",
167 &auto_explain_log_wal
,
175 DefineCustomBoolVariable("auto_explain.log_triggers",
176 "Include trigger statistics in plans.",
177 "This has no effect unless log_analyze is also set.",
178 &auto_explain_log_triggers
,
186 DefineCustomEnumVariable("auto_explain.log_format",
187 "EXPLAIN format to be used for plan logging.",
189 &auto_explain_log_format
,
198 DefineCustomEnumVariable("auto_explain.log_level",
199 "Log level for the plan.",
201 &auto_explain_log_level
,
210 DefineCustomBoolVariable("auto_explain.log_nested_statements",
211 "Log nested statements.",
213 &auto_explain_log_nested_statements
,
221 DefineCustomBoolVariable("auto_explain.log_timing",
222 "Collect timing data, not just row counts.",
224 &auto_explain_log_timing
,
232 DefineCustomRealVariable("auto_explain.sample_rate",
233 "Fraction of queries to process.",
235 &auto_explain_sample_rate
,
245 MarkGUCPrefixReserved("auto_explain");
248 prev_ExecutorStart
= ExecutorStart_hook
;
249 ExecutorStart_hook
= explain_ExecutorStart
;
250 prev_ExecutorRun
= ExecutorRun_hook
;
251 ExecutorRun_hook
= explain_ExecutorRun
;
252 prev_ExecutorFinish
= ExecutorFinish_hook
;
253 ExecutorFinish_hook
= explain_ExecutorFinish
;
254 prev_ExecutorEnd
= ExecutorEnd_hook
;
255 ExecutorEnd_hook
= explain_ExecutorEnd
;
259 * ExecutorStart hook: start up logging if needed
262 explain_ExecutorStart(QueryDesc
*queryDesc
, int eflags
)
265 * At the beginning of each top-level statement, decide whether we'll
266 * sample this statement. If nested-statement explaining is enabled,
267 * either all nested statements will be explained or none will.
269 * When in a parallel worker, we should do nothing, which we can implement
270 * cheaply by pretending we decided not to sample the current statement.
271 * If EXPLAIN is active in the parent session, data will be collected and
272 * reported back to the parent, and it's no business of ours to interfere.
274 if (nesting_level
== 0)
276 if (auto_explain_log_min_duration
>= 0 && !IsParallelWorker())
277 current_query_sampled
= (pg_prng_double(&pg_global_prng_state
) < auto_explain_sample_rate
);
279 current_query_sampled
= false;
282 if (auto_explain_enabled())
284 /* Enable per-node instrumentation iff log_analyze is required. */
285 if (auto_explain_log_analyze
&& (eflags
& EXEC_FLAG_EXPLAIN_ONLY
) == 0)
287 if (auto_explain_log_timing
)
288 queryDesc
->instrument_options
|= INSTRUMENT_TIMER
;
290 queryDesc
->instrument_options
|= INSTRUMENT_ROWS
;
291 if (auto_explain_log_buffers
)
292 queryDesc
->instrument_options
|= INSTRUMENT_BUFFERS
;
293 if (auto_explain_log_wal
)
294 queryDesc
->instrument_options
|= INSTRUMENT_WAL
;
298 if (prev_ExecutorStart
)
299 prev_ExecutorStart(queryDesc
, eflags
);
301 standard_ExecutorStart(queryDesc
, eflags
);
303 if (auto_explain_enabled())
306 * Set up to track total elapsed time in ExecutorRun. Make sure the
307 * space is allocated in the per-query context so it will go away at
310 if (queryDesc
->totaltime
== NULL
)
312 MemoryContext oldcxt
;
314 oldcxt
= MemoryContextSwitchTo(queryDesc
->estate
->es_query_cxt
);
315 queryDesc
->totaltime
= InstrAlloc(1, INSTRUMENT_ALL
, false);
316 MemoryContextSwitchTo(oldcxt
);
322 * ExecutorRun hook: all we need do is track nesting depth
325 explain_ExecutorRun(QueryDesc
*queryDesc
, ScanDirection direction
,
326 uint64 count
, bool execute_once
)
331 if (prev_ExecutorRun
)
332 prev_ExecutorRun(queryDesc
, direction
, count
, execute_once
);
334 standard_ExecutorRun(queryDesc
, direction
, count
, execute_once
);
344 * ExecutorFinish hook: all we need do is track nesting depth
347 explain_ExecutorFinish(QueryDesc
*queryDesc
)
352 if (prev_ExecutorFinish
)
353 prev_ExecutorFinish(queryDesc
);
355 standard_ExecutorFinish(queryDesc
);
365 * ExecutorEnd hook: log results if needed
368 explain_ExecutorEnd(QueryDesc
*queryDesc
)
370 if (queryDesc
->totaltime
&& auto_explain_enabled())
372 MemoryContext oldcxt
;
376 * Make sure we operate in the per-query context, so any cruft will be
377 * discarded later during ExecutorEnd.
379 oldcxt
= MemoryContextSwitchTo(queryDesc
->estate
->es_query_cxt
);
382 * Make sure stats accumulation is done. (Note: it's okay if several
383 * levels of hook all do this.)
385 InstrEndLoop(queryDesc
->totaltime
);
387 /* Log plan if duration is exceeded. */
388 msec
= queryDesc
->totaltime
->total
* 1000.0;
389 if (msec
>= auto_explain_log_min_duration
)
391 ExplainState
*es
= NewExplainState();
393 es
->analyze
= (queryDesc
->instrument_options
&& auto_explain_log_analyze
);
394 es
->verbose
= auto_explain_log_verbose
;
395 es
->buffers
= (es
->analyze
&& auto_explain_log_buffers
);
396 es
->wal
= (es
->analyze
&& auto_explain_log_wal
);
397 es
->timing
= (es
->analyze
&& auto_explain_log_timing
);
398 es
->summary
= es
->analyze
;
399 /* No support for MEMORY */
400 /* es->memory = false; */
401 es
->format
= auto_explain_log_format
;
402 es
->settings
= auto_explain_log_settings
;
404 ExplainBeginOutput(es
);
405 ExplainQueryText(es
, queryDesc
);
406 ExplainQueryParameters(es
, queryDesc
->params
, auto_explain_log_parameter_max_length
);
407 ExplainPrintPlan(es
, queryDesc
);
408 if (es
->analyze
&& auto_explain_log_triggers
)
409 ExplainPrintTriggers(es
, queryDesc
);
411 ExplainPrintJITSummary(es
, queryDesc
);
412 ExplainEndOutput(es
);
414 /* Remove last line break */
415 if (es
->str
->len
> 0 && es
->str
->data
[es
->str
->len
- 1] == '\n')
416 es
->str
->data
[--es
->str
->len
] = '\0';
418 /* Fix JSON to output an object */
419 if (auto_explain_log_format
== EXPLAIN_FORMAT_JSON
)
421 es
->str
->data
[0] = '{';
422 es
->str
->data
[es
->str
->len
- 1] = '}';
426 * Note: we rely on the existing logging of context or
427 * debug_query_string to identify just which statement is being
428 * reported. This isn't ideal but trying to do it here would
429 * often result in duplication.
431 ereport(auto_explain_log_level
,
432 (errmsg("duration: %.3f ms plan:\n%s",
433 msec
, es
->str
->data
),
437 MemoryContextSwitchTo(oldcxt
);
440 if (prev_ExecutorEnd
)
441 prev_ExecutorEnd(queryDesc
);
443 standard_ExecutorEnd(queryDesc
);