Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit af7914c

Browse files
committed
Add TIMING option to EXPLAIN, to allow eliminating of timing overhead.
Sometimes it may be useful to get actual row counts out of EXPLAIN (ANALYZE) without paying the cost of timing every node entry/exit. With this patch, you can say EXPLAIN (ANALYZE, TIMING OFF) to get that. Tomas Vondra, reviewed by Eric Theise, with minor doc changes by me.
1 parent 1631598 commit af7914c

File tree

6 files changed

+97
-22
lines changed

6 files changed

+97
-22
lines changed

contrib/auto_explain/auto_explain.c

+18-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ static int auto_explain_log_min_duration = -1; /* msec or -1 */
2323
static bool auto_explain_log_analyze = false;
2424
static bool auto_explain_log_verbose = false;
2525
static bool auto_explain_log_buffers = false;
26+
static bool auto_explain_log_timing = false;
2627
static int auto_explain_log_format = EXPLAIN_FORMAT_TEXT;
2728
static bool auto_explain_log_nested_statements = false;
2829

@@ -133,6 +134,17 @@ _PG_init(void)
133134
NULL,
134135
NULL);
135136

137+
DefineCustomBoolVariable("auto_explain.log_timing",
138+
"Collect timing data, not just row counts.",
139+
NULL,
140+
&auto_explain_log_timing,
141+
true,
142+
PGC_SUSET,
143+
0,
144+
NULL,
145+
NULL,
146+
NULL);
147+
136148
EmitWarningsOnPlaceholders("auto_explain");
137149

138150
/* Install hooks. */
@@ -170,7 +182,12 @@ explain_ExecutorStart(QueryDesc *queryDesc, int eflags)
170182
/* Enable per-node instrumentation iff log_analyze is required. */
171183
if (auto_explain_log_analyze && (eflags & EXEC_FLAG_EXPLAIN_ONLY) == 0)
172184
{
173-
queryDesc->instrument_options |= INSTRUMENT_TIMER;
185+
if (auto_explain_log_timing)
186+
queryDesc->instrument_options |= INSTRUMENT_TIMER;
187+
else
188+
queryDesc->instrument_options |= INSTRUMENT_ROWS;
189+
190+
174191
if (auto_explain_log_buffers)
175192
queryDesc->instrument_options |= INSTRUMENT_BUFFERS;
176193
}

doc/src/sgml/ref/explain.sgml

+16
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replac
4040
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
4141
COSTS [ <replaceable class="parameter">boolean</replaceable> ]
4242
BUFFERS [ <replaceable class="parameter">boolean</replaceable> ]
43+
TIMING [ <replaceable class="parameter">boolean</replaceable> ]
4344
FORMAT { TEXT | XML | JSON | YAML }
4445
</synopsis>
4546
</refsynopsisdiv>
@@ -171,6 +172,21 @@ ROLLBACK;
171172
</listitem>
172173
</varlistentry>
173174

175+
<varlistentry>
176+
<term><literal>TIMING</literal></term>
177+
<listitem>
178+
<para>
179+
Include the actual startup time and time spent in the node in the output.
180+
The overhead of repeatedly reading the system clock can slow down the
181+
query significantly on some systems, so it may be useful to set this
182+
parameter to <literal>FALSE</literal> when only actual row counts, and not
183+
exact times, are needed.
184+
This parameter may only be used when <literal>ANALYZE</literal> is also
185+
enabled. It defaults to <literal>TRUE</literal>.
186+
</para>
187+
</listitem>
188+
</varlistentry>
189+
174190
<varlistentry>
175191
<term><literal>FORMAT</literal></term>
176192
<listitem>

src/backend/commands/explain.c

+38-7
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
116116
TupOutputState *tstate;
117117
List *rewritten;
118118
ListCell *lc;
119+
bool timing_set = false;
119120

120121
/* Initialize ExplainState. */
121122
ExplainInitState(&es);
@@ -133,6 +134,11 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
133134
es.costs = defGetBoolean(opt);
134135
else if (strcmp(opt->defname, "buffers") == 0)
135136
es.buffers = defGetBoolean(opt);
137+
else if (strcmp(opt->defname, "timing") == 0)
138+
{
139+
timing_set = true;
140+
es.timing = defGetBoolean(opt);
141+
}
136142
else if (strcmp(opt->defname, "format") == 0)
137143
{
138144
char *p = defGetString(opt);
@@ -162,6 +168,15 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
162168
ereport(ERROR,
163169
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
164170
errmsg("EXPLAIN option BUFFERS requires ANALYZE")));
171+
172+
/* if the timing was not set explicitly, set default value */
173+
es.timing = (timing_set) ? es.timing : es.analyze;
174+
175+
/* check that timing is used with EXPLAIN ANALYZE */
176+
if (es.timing && !es.analyze)
177+
ereport(ERROR,
178+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
179+
errmsg("EXPLAIN option TIMING requires ANALYZE")));
165180

166181
/*
167182
* Parse analysis was done already, but we still have to run the rule
@@ -360,8 +375,11 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
360375
int eflags;
361376
int instrument_option = 0;
362377

363-
if (es->analyze)
378+
if (es->analyze && es->timing)
364379
instrument_option |= INSTRUMENT_TIMER;
380+
else if (es->analyze)
381+
instrument_option |= INSTRUMENT_ROWS;
382+
365383
if (es->buffers)
366384
instrument_option |= INSTRUMENT_BUFFERS;
367385

@@ -956,29 +974,42 @@ ExplainNode(PlanState *planstate, List *ancestors,
956974

957975
if (es->format == EXPLAIN_FORMAT_TEXT)
958976
{
959-
appendStringInfo(es->str,
960-
" (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
961-
startup_sec, total_sec, rows, nloops);
977+
if (planstate->instrument->need_timer)
978+
appendStringInfo(es->str,
979+
" (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
980+
startup_sec, total_sec, rows, nloops);
981+
else
982+
appendStringInfo(es->str,
983+
" (actual rows=%.0f loops=%.0f)",
984+
rows, nloops);
962985
}
963986
else
964987
{
965-
ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
966-
ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
988+
if (planstate->instrument->need_timer)
989+
{
990+
ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
991+
ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
992+
}
967993
ExplainPropertyFloat("Actual Rows", rows, 0, es);
968994
ExplainPropertyFloat("Actual Loops", nloops, 0, es);
969995
}
970996
}
971997
else if (es->analyze)
972998
{
999+
9731000
if (es->format == EXPLAIN_FORMAT_TEXT)
9741001
appendStringInfo(es->str, " (never executed)");
975-
else
1002+
else if (planstate->instrument->need_timer)
9761003
{
9771004
ExplainPropertyFloat("Actual Startup Time", 0.0, 3, es);
9781005
ExplainPropertyFloat("Actual Total Time", 0.0, 3, es);
1006+
}
1007+
else
1008+
{
9791009
ExplainPropertyFloat("Actual Rows", 0.0, 0, es);
9801010
ExplainPropertyFloat("Actual Loops", 0.0, 0, es);
9811011
}
1012+
9821013
}
9831014

9841015
/* in text format, first line ends here */

src/backend/executor/instrument.c

+21-13
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,19 @@ InstrAlloc(int n, int instrument_options)
2929
{
3030
Instrumentation *instr;
3131

32-
/* timer is always required for now */
33-
Assert(instrument_options & INSTRUMENT_TIMER);
34-
3532
/* initialize all fields to zeroes, then modify as needed */
3633
instr = palloc0(n * sizeof(Instrumentation));
37-
if (instrument_options & INSTRUMENT_BUFFERS)
34+
if (instrument_options & (INSTRUMENT_BUFFERS | INSTRUMENT_TIMER))
3835
{
3936
int i;
37+
bool need_buffers = instrument_options & INSTRUMENT_BUFFERS;
38+
bool need_timer = instrument_options & INSTRUMENT_TIMER;
4039

4140
for (i = 0; i < n; i++)
42-
instr[i].need_bufusage = true;
41+
{
42+
instr[i].need_bufusage = need_buffers;
43+
instr[i].need_timer = need_timer;
44+
}
4345
}
4446

4547
return instr;
@@ -49,7 +51,7 @@ InstrAlloc(int n, int instrument_options)
4951
void
5052
InstrStartNode(Instrumentation *instr)
5153
{
52-
if (INSTR_TIME_IS_ZERO(instr->starttime))
54+
if (instr->need_timer && INSTR_TIME_IS_ZERO(instr->starttime))
5355
INSTR_TIME_SET_CURRENT(instr->starttime);
5456
else
5557
elog(DEBUG2, "InstrStartNode called twice in a row");
@@ -68,16 +70,22 @@ InstrStopNode(Instrumentation *instr, double nTuples)
6870
/* count the returned tuples */
6971
instr->tuplecount += nTuples;
7072

71-
if (INSTR_TIME_IS_ZERO(instr->starttime))
73+
/* let's update the time only if the timer was requested */
74+
if (instr->need_timer)
7275
{
73-
elog(DEBUG2, "InstrStopNode called without start");
74-
return;
75-
}
7676

77-
INSTR_TIME_SET_CURRENT(endtime);
78-
INSTR_TIME_ACCUM_DIFF(instr->counter, endtime, instr->starttime);
77+
if (INSTR_TIME_IS_ZERO(instr->starttime))
78+
{
79+
elog(DEBUG2, "InstrStopNode called without start");
80+
return;
81+
}
7982

80-
INSTR_TIME_SET_ZERO(instr->starttime);
83+
INSTR_TIME_SET_CURRENT(endtime);
84+
INSTR_TIME_ACCUM_DIFF(instr->counter, endtime, instr->starttime);
85+
86+
INSTR_TIME_SET_ZERO(instr->starttime);
87+
88+
}
8189

8290
/* Add delta of buffer usage since entry to node's totals */
8391
if (instr->need_bufusage)

src/include/commands/explain.h

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ typedef struct ExplainState
3131
bool analyze; /* print actual times */
3232
bool costs; /* print costs */
3333
bool buffers; /* print buffer usage */
34+
bool timing; /* print timing */
3435
ExplainFormat format; /* output format */
3536
/* other states */
3637
PlannedStmt *pstmt; /* top of plan */

src/include/executor/instrument.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ typedef struct BufferUsage
3131
/* Flag bits included in InstrAlloc's instrument_options bitmask */
3232
typedef enum InstrumentOption
3333
{
34-
INSTRUMENT_TIMER = 1 << 0, /* needs timer */
34+
INSTRUMENT_TIMER = 1 << 0, /* needs timer (and row counts) */
3535
INSTRUMENT_BUFFERS = 1 << 1, /* needs buffer usage */
36+
INSTRUMENT_ROWS = 1 << 2, /* needs row count */
3637
INSTRUMENT_ALL = 0x7FFFFFFF
3738
} InstrumentOption;
3839

3940
typedef struct Instrumentation
4041
{
4142
/* Parameters set at node creation: */
43+
bool need_timer; /* TRUE if we need timer data */
4244
bool need_bufusage; /* TRUE if we need buffer usage data */
4345
/* Info about current plan cycle: */
4446
bool running; /* TRUE if we've completed first tuple */

0 commit comments

Comments
 (0)