On 23.12.2011 14:57, Robert Haas wrote: > 2011/12/22 Tomas Vondra <t...@fuzzy.cz>: >> The gettimeofday() calls are not exactly cheap in some cases, so why to >> pay that price when all you need is the number of rows? > > Fair point. > >> The patch attached does this: >> >> 1) adds INSTRUMENT_ROWS, a new InstrumentOption >> >> - counts rows without timing (no gettimeofday() callse) >> - if you want timing info, use INSTRUMENT_TIMER >> >> 2) adds new option "TIMING" to EXPLAIN, i.e. >> >> EXPLAIN (ANALYZE ON, TIMING ON) SELECT ... >> >> 3) adds auto_explain.log_rows_only (false by default) >> >> - if you set this to 'true', then the instrumentation will just >> count rows, without calling gettimeofday() > > This seems like an unnecessarily confusing interface, because you've > named the auto_explain option differently from the EXPLAIN option and > given it (almost) the opposite sense: timing=off means the same thing > as log_rows_only=on. > > I think the EXPLAIN (TIMING) option is good the way you have it, but > then just have auto_explain.log_timing, with a default value of on.
Makes sense. I've updated the patch to reflect this, so the option is called auto_explain.log_timing and is true by default. I'll add the patch to the next commit fest. One thing I'm wondering about is that the InstrumentOptions are not exclusive - INSTRUMENT_TIMER means 'collect timing and row counts' while INSTRUMENT_ROWS means 'collect row counts'. Wouldn't it be better to redefine the INSTRUMENT_TIMER so that it collects just timing info. I.e. to get the current behaviour, you'd have to do this instrument_options |= (INSTRUMENT_TIMER | INSTRUMENT_ROWS) It's quite trivial change in explain.c, the problem I have with that is that it might break extensions. Tomas
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index b320698..4b52b26 100644 --- a/contrib/auto_explain/auto_explain.c +++ b/contrib/auto_explain/auto_explain.c @@ -23,6 +23,7 @@ static int auto_explain_log_min_duration = -1; /* msec or -1 */ static bool auto_explain_log_analyze = false; static bool auto_explain_log_verbose = false; static bool auto_explain_log_buffers = false; +static bool auto_explain_log_timing = false; static int auto_explain_log_format = EXPLAIN_FORMAT_TEXT; static bool auto_explain_log_nested_statements = false; @@ -133,6 +134,17 @@ _PG_init(void) NULL, NULL); + DefineCustomBoolVariable("auto_explain.log_timing", + "Collect timing data, not just row counts.", + NULL, + &auto_explain_log_timing, + true, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + EmitWarningsOnPlaceholders("auto_explain"); /* Install hooks. */ @@ -170,7 +182,12 @@ explain_ExecutorStart(QueryDesc *queryDesc, int eflags) /* Enable per-node instrumentation iff log_analyze is required. */ if (auto_explain_log_analyze && (eflags & EXEC_FLAG_EXPLAIN_ONLY) == 0) { - queryDesc->instrument_options |= INSTRUMENT_TIMER; + if (auto_explain_log_timing) + queryDesc->instrument_options |= INSTRUMENT_TIMER; + else + queryDesc->instrument_options |= INSTRUMENT_ROWS; + + if (auto_explain_log_buffers) queryDesc->instrument_options |= INSTRUMENT_BUFFERS; } diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml index 8a9c9de..4488956 100644 --- a/doc/src/sgml/ref/explain.sgml +++ b/doc/src/sgml/ref/explain.sgml @@ -40,6 +40,7 @@ EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replac VERBOSE [ <replaceable class="parameter">boolean</replaceable> ] COSTS [ <replaceable class="parameter">boolean</replaceable> ] BUFFERS [ <replaceable class="parameter">boolean</replaceable> ] + TIMING [ <replaceable class="parameter">boolean</replaceable> ] FORMAT { TEXT | XML | JSON | YAML } </synopsis> </refsynopsisdiv> @@ -172,6 +173,20 @@ ROLLBACK; </varlistentry> <varlistentry> + <term><literal>TIMING</literal></term> + <listitem> + <para> + Include information on timing for each node. Specifically, include the + actual startup time and time spent in the node. This may involve a lot + of gettimeofday calls, and on some systems this means significant + slowdown of the query. + This parameter may only be used when <literal>ANALYZE</literal> is also + enabled. It defaults to <literal>TRUE</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><literal>FORMAT</literal></term> <listitem> <para> diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index e38de5c..468bb56 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -133,6 +133,8 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, es.costs = defGetBoolean(opt); else if (strcmp(opt->defname, "buffers") == 0) es.buffers = defGetBoolean(opt); + else if (strcmp(opt->defname, "timing") == 0) + es.timing = defGetBoolean(opt); else if (strcmp(opt->defname, "format") == 0) { char *p = defGetString(opt); @@ -163,6 +165,11 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("EXPLAIN option BUFFERS requires ANALYZE"))); + if (es.timing && !es.analyze) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("EXPLAIN option TIMING requires ANALYZE"))); + /* * Parse analysis was done already, but we still have to run the rule * rewriter. We do not do AcquireRewriteLocks: we assume the query either @@ -229,6 +236,7 @@ ExplainInitState(ExplainState *es) /* Set default options. */ memset(es, 0, sizeof(ExplainState)); es->costs = true; + es->timing = true; /* Prepare output buffer. */ es->str = makeStringInfo(); } @@ -355,8 +363,11 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, int eflags; int instrument_option = 0; - if (es->analyze) + if (es->analyze && es->timing) instrument_option |= INSTRUMENT_TIMER; + else if (es->analyze) + instrument_option |= INSTRUMENT_ROWS; + if (es->buffers) instrument_option |= INSTRUMENT_BUFFERS; @@ -951,29 +962,42 @@ ExplainNode(PlanState *planstate, List *ancestors, if (es->format == EXPLAIN_FORMAT_TEXT) { - appendStringInfo(es->str, - " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)", - startup_sec, total_sec, rows, nloops); + if (planstate->instrument->need_timer) + appendStringInfo(es->str, + " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)", + startup_sec, total_sec, rows, nloops); + else + appendStringInfo(es->str, + " (actual rows=%.0f loops=%.0f)", + rows, nloops); } else { - ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es); - ExplainPropertyFloat("Actual Total Time", total_sec, 3, es); + if (planstate->instrument->need_timer) + { + ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es); + ExplainPropertyFloat("Actual Total Time", total_sec, 3, es); + } ExplainPropertyFloat("Actual Rows", rows, 0, es); ExplainPropertyFloat("Actual Loops", nloops, 0, es); } } else if (es->analyze) { + if (es->format == EXPLAIN_FORMAT_TEXT) appendStringInfo(es->str, " (never executed)"); - else + else if (planstate->instrument->need_timer) { ExplainPropertyFloat("Actual Startup Time", 0.0, 3, es); ExplainPropertyFloat("Actual Total Time", 0.0, 3, es); + } + else + { ExplainPropertyFloat("Actual Rows", 0.0, 0, es); ExplainPropertyFloat("Actual Loops", 0.0, 0, es); } + } /* in text format, first line ends here */ diff --git a/src/backend/executor/instrument.c b/src/backend/executor/instrument.c index 9d30200..982a961 100644 --- a/src/backend/executor/instrument.c +++ b/src/backend/executor/instrument.c @@ -29,17 +29,21 @@ InstrAlloc(int n, int instrument_options) { Instrumentation *instr; - /* timer is always required for now */ - Assert(instrument_options & INSTRUMENT_TIMER); - /* initialize all fields to zeroes, then modify as needed */ instr = palloc0(n * sizeof(Instrumentation)); - if (instrument_options & INSTRUMENT_BUFFERS) + if (instrument_options & (INSTRUMENT_BUFFERS | INSTRUMENT_TIMER)) { int i; + bool need_buffers = instrument_options & INSTRUMENT_BUFFERS; + bool need_timer = instrument_options & INSTRUMENT_TIMER; + + elog(WARNING, "need_timer = %d", need_timer ? 1 : 0); for (i = 0; i < n; i++) - instr[i].need_bufusage = true; + { + instr[i].need_bufusage = need_buffers; + instr[i].need_timer = need_timer; + } } return instr; @@ -49,7 +53,7 @@ InstrAlloc(int n, int instrument_options) void InstrStartNode(Instrumentation *instr) { - if (INSTR_TIME_IS_ZERO(instr->starttime)) + if (instr->need_timer && INSTR_TIME_IS_ZERO(instr->starttime)) INSTR_TIME_SET_CURRENT(instr->starttime); else elog(DEBUG2, "InstrStartNode called twice in a row"); @@ -68,16 +72,22 @@ InstrStopNode(Instrumentation *instr, double nTuples) /* count the returned tuples */ instr->tuplecount += nTuples; - if (INSTR_TIME_IS_ZERO(instr->starttime)) + /* let's update the time only if the timer was requested */ + if (instr->need_timer) { - elog(DEBUG2, "InstrStopNode called without start"); - return; - } - INSTR_TIME_SET_CURRENT(endtime); - INSTR_TIME_ACCUM_DIFF(instr->counter, endtime, instr->starttime); + if (INSTR_TIME_IS_ZERO(instr->starttime)) + { + elog(DEBUG2, "InstrStopNode called without start"); + return; + } - INSTR_TIME_SET_ZERO(instr->starttime); + INSTR_TIME_SET_CURRENT(endtime); + INSTR_TIME_ACCUM_DIFF(instr->counter, endtime, instr->starttime); + + INSTR_TIME_SET_ZERO(instr->starttime); + + } /* Add delta of buffer usage since entry to node's totals */ if (instr->need_bufusage) diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index d7998c3..1c80bf3 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -31,6 +31,7 @@ typedef struct ExplainState bool analyze; /* print actual times */ bool costs; /* print costs */ bool buffers; /* print buffer usage */ + bool timing; /* print timing */ ExplainFormat format; /* output format */ /* other states */ PlannedStmt *pstmt; /* top of plan */ diff --git a/src/include/executor/instrument.h b/src/include/executor/instrument.h index 22c3106..671e897 100644 --- a/src/include/executor/instrument.h +++ b/src/include/executor/instrument.h @@ -31,14 +31,16 @@ typedef struct BufferUsage /* Flag bits included in InstrAlloc's instrument_options bitmask */ typedef enum InstrumentOption { - INSTRUMENT_TIMER = 1 << 0, /* needs timer */ + INSTRUMENT_TIMER = 1 << 0, /* needs timer (and row counts) */ INSTRUMENT_BUFFERS = 1 << 1, /* needs buffer usage */ + INSTRUMENT_ROWS = 1 << 2, /* needs row count */ INSTRUMENT_ALL = 0x7FFFFFFF } InstrumentOption; typedef struct Instrumentation { /* Parameters set at node creation: */ + bool need_timer; /* TRUE if we need timer data */ bool need_bufusage; /* TRUE if we need buffer usage data */ /* Info about current plan cycle: */ bool running; /* TRUE if we've completed first tuple */
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers