diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml index 6e82d82aad169e96444344df40333a942b70fc08..6628c9f4925b269685c080edd9901a6008980cc0 100644 --- a/doc/src/sgml/perform.sgml +++ b/doc/src/sgml/perform.sgml @@ -1,5 +1,5 @@ <!-- -$Header: /cvsroot/pgsql/doc/src/sgml/perform.sgml,v 1.9 2001/09/13 15:55:23 petere Exp $ +$Header: /cvsroot/pgsql/doc/src/sgml/perform.sgml,v 1.10 2001/09/18 01:59:05 tgl Exp $ --> <chapter id="performance-tips"> @@ -122,7 +122,7 @@ select * from pg_class where relname = 'tenk1'; regression=# explain select * from tenk1 where unique1 < 1000; NOTICE: QUERY PLAN: -Seq Scan on tenk1 (cost=0.00..358.00 rows=1003 width=148) +Seq Scan on tenk1 (cost=0.00..358.00 rows=1007 width=148) </programlisting> The estimate of output rows has gone down because of the WHERE clause. @@ -147,7 +147,7 @@ Seq Scan on tenk1 (cost=0.00..358.00 rows=1003 width=148) regression=# explain select * from tenk1 where unique1 < 50; NOTICE: QUERY PLAN: -Index Scan using tenk1_unique1 on tenk1 (cost=0.00..173.32 rows=47 width=148) +Index Scan using tenk1_unique1 on tenk1 (cost=0.00..181.09 rows=49 width=148) </programlisting> and you will see that if we make the WHERE condition selective @@ -166,7 +166,7 @@ regression=# explain select * from tenk1 where unique1 < 50 and regression-# stringu1 = 'xxx'; NOTICE: QUERY PLAN: -Index Scan using tenk1_unique1 on tenk1 (cost=0.00..173.44 rows=1 width=148) +Index Scan using tenk1_unique1 on tenk1 (cost=0.00..181.22 rows=1 width=148) </programlisting> The added clause <literal>stringu1 = 'xxx'</literal> reduces the output-rows estimate, @@ -181,11 +181,11 @@ regression=# explain select * from tenk1 t1, tenk2 t2 where t1.unique1 < 50 regression-# and t1.unique2 = t2.unique2; NOTICE: QUERY PLAN: -Nested Loop (cost=0.00..269.11 rows=47 width=296) +Nested Loop (cost=0.00..330.41 rows=49 width=296) -> Index Scan using tenk1_unique1 on tenk1 t1 - (cost=0.00..173.32 rows=47 width=148) + (cost=0.00..181.09 rows=49 width=148) -> Index Scan using tenk2_unique2 on tenk2 t2 - (cost=0.00..2.01 rows=1 width=148) + (cost=0.00..3.01 rows=1 width=148) </programlisting> </para> @@ -202,7 +202,7 @@ Nested Loop (cost=0.00..269.11 rows=47 width=296) same inner-scan plan and costs that we'd get from, say, <literal>explain select * from tenk2 where unique2 = 42</literal>. The costs of the loop node are then set on the basis of the cost of the outer scan, plus one repetition of the - inner scan for each outer tuple (47 * 2.01, here), plus a little CPU + inner scan for each outer tuple (49 * 3.01, here), plus a little CPU time for join processing. </para> @@ -229,12 +229,12 @@ regression=# explain select * from tenk1 t1, tenk2 t2 where t1.unique1 < 50 regression-# and t1.unique2 = t2.unique2; NOTICE: QUERY PLAN: -Hash Join (cost=173.44..557.03 rows=47 width=296) +Hash Join (cost=181.22..564.83 rows=49 width=296) -> Seq Scan on tenk2 t2 (cost=0.00..333.00 rows=10000 width=148) - -> Hash (cost=173.32..173.32 rows=47 width=148) + -> Hash (cost=181.09..181.09 rows=49 width=148) -> Index Scan using tenk1_unique1 on tenk1 t1 - (cost=0.00..173.32 rows=47 width=148) + (cost=0.00..181.09 rows=49 width=148) </programlisting> This plan proposes to extract the 50 interesting rows of <classname>tenk1</classname> @@ -245,10 +245,61 @@ Hash Join (cost=173.44..557.03 rows=47 width=296) cost for the hash join, since we won't get any tuples out until we can start reading <classname>tenk2</classname>. The total time estimate for the join also includes a hefty charge for CPU time to probe the hash table - 10000 times. Note, however, that we are NOT charging 10000 times 173.32; + 10000 times. Note, however, that we are NOT charging 10000 times 181.09; the hash table setup is only done once in this plan type. </para> + <para> + It is possible to check on the accuracy of the planner's estimated costs + by using EXPLAIN ANALYZE. This command actually executes the query, + and then displays the true runtime accumulated within each plan node + along with the same estimated costs that a plain EXPLAIN shows. + For example, we might get a result like this: + + <programlisting> +regression=# explain analyze select * from tenk1 t1, tenk2 t2 where t1.unique1 < 50 +regression-# and t1.unique2 = t2.unique2; +NOTICE: QUERY PLAN: + +Nested Loop (cost=0.00..330.41 rows=49 width=296) (actual time=1.31..28.90 rows=50 loops=1) + -> Index Scan using tenk1_unique1 on tenk1 t1 + (cost=0.00..181.09 rows=49 width=148) (actual time=0.69..8.84 rows=50 loops=1) + -> Index Scan using tenk2_unique2 on tenk2 t2 + (cost=0.00..3.01 rows=1 width=148) (actual time=0.28..0.31 rows=1 loops=50) +Total runtime: 30.67 msec + </programlisting> + + Note that the <quote>actual time</quote> values are in milliseconds of + real time, whereas the <quote>cost</quote> estimates are expressed in + arbitrary units of disk fetches; so they are unlikely to match up. + The thing to pay attention to is the ratios. + </para> + + <para> + In some query plans, it is possible for a subplan node to be executed more + than once. For example, the inner indexscan is executed once per outer + tuple in the above nested-loop plan. In such cases, the + <quote>loops</quote> value reports the + total number of executions of the node, and the actual time and rows + values shown are averages per-execution. This is done to make the numbers + comparable with the way that the cost estimates are shown. Multiply by + the <quote>loops</quote> value to get the total time actually spent in + the node. + </para> + + <para> + The <quote>total runtime</quote> shown by EXPLAIN ANALYZE includes + executor startup and shutdown time, as well as time spent processing + the result tuples. It does not include parsing, rewriting, or planning + time. For a SELECT query, the total runtime will normally be just a + little larger than the total time reported for the top-level plan node. + For INSERT, UPDATE, and DELETE queries, the total runtime may be + considerably larger, because it includes the time spent processing the + output tuples. In these queries, the time for the top plan node + essentially is the time spent computing the new tuples and/or locating + the old ones, but it doesn't include the time spent making the changes. + </para> + <para> It is worth noting that EXPLAIN results should not be extrapolated to situations other than the one you are actually testing; for example, diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml index 7bccea72ad772eaa1bc0350811ceb4a533965cec..b24863529b3eb9ebf3242911e632451d46fb5729 100644 --- a/doc/src/sgml/ref/explain.sgml +++ b/doc/src/sgml/ref/explain.sgml @@ -1,5 +1,5 @@ <!-- -$Header: /cvsroot/pgsql/doc/src/sgml/ref/explain.sgml,v 1.12 2001/09/03 12:57:50 petere Exp $ +$Header: /cvsroot/pgsql/doc/src/sgml/ref/explain.sgml,v 1.13 2001/09/18 01:59:05 tgl Exp $ Postgres documentation --> @@ -24,7 +24,7 @@ Postgres documentation <date>1999-07-20</date> </refsynopsisdivinfo> <synopsis> -EXPLAIN [ VERBOSE ] <replaceable class="PARAMETER">query</replaceable> +EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="PARAMETER">query</replaceable> </synopsis> <refsect2 id="R2-SQL-EXPLAIN-1"> @@ -37,6 +37,14 @@ EXPLAIN [ VERBOSE ] <replaceable class="PARAMETER">query</replaceable> <para> <variablelist> + <varlistentry> + <term>ANALYZE</term> + <listitem> + <para> + Flag to carry out the query and show actual runtimes. + </para> + </listitem> + </varlistentry> <varlistentry> <term>VERBOSE</term> <listitem> @@ -124,12 +132,37 @@ EXPLAIN costs to estimate which plan is really the cheapest. </para> + <para> + The ANALYZE option causes the query to be actually executed, not only + planned. The total elapsed time expended within each plan node (in + milliseconds) and total number of rows it actually returned are added to + the display. This is useful for seeing whether the planner's estimates + are close to reality. + </para> + <para> The VERBOSE option emits the full internal representation of the plan tree, rather than just a summary (and sends it to the postmaster log file, too). Usually this option is only useful for debugging Postgres. </para> + <caution> + <para> + Keep in mind that the query is actually executed when ANALYZE is used. + Although <command>EXPLAIN</command> will discard any output that a SELECT + would return, + other side-effects of the query will happen as usual. + If you wish to use <command>EXPLAIN ANALYZE</command> on an INSERT, + UPDATE, or DELETE query without letting the query affect your data, + use this approach: + <programlisting> +BEGIN; +EXPLAIN ANALYZE ...; +ROLLBACK; + </programlisting> + </para> + </caution> + <refsect2 id="R2-SQL-EXPLAIN-3"> <refsect2info> <date>1998-04-15</date> @@ -140,11 +173,8 @@ EXPLAIN <para> There is only sparse documentation on the optimizer's use of cost information in <productname>Postgres</productname>. - General information on cost estimation for query optimization - can be found in database textbooks. - Refer to the <citetitle>Programmer's Guide</citetitle> - in the chapters on indexes and the genetic query optimizer for - more information. + Refer to the <citetitle>User's Guide</citetitle> and + <citetitle>Programmer's Guide</citetitle> for more information. </para> </refsect2> </refsect1> diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 672ec54cb029d283447ff9cbcd0c0a6ee64b4ffa..91282ffc3f60465688c25f50452c3e698968749c 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -5,18 +5,20 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.65 2001/03/22 03:59:22 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.66 2001/09/18 01:59:06 tgl Exp $ * */ #include "postgres.h" #include "commands/explain.h" +#include "executor/instrument.h" #include "lib/stringinfo.h" #include "nodes/print.h" #include "optimizer/planner.h" #include "parser/parsetree.h" #include "rewrite/rewriteHandler.h" +#include "tcop/pquery.h" #include "utils/relcache.h" typedef struct ExplainState @@ -28,8 +30,8 @@ typedef struct ExplainState List *rtable; /* range table */ } ExplainState; -static char *Explain_PlanToString(Plan *plan, ExplainState *es); -static void ExplainOneQuery(Query *query, bool verbose, CommandDest dest); +static StringInfo Explain_PlanToString(Plan *plan, ExplainState *es); +static void ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest); /* Convert a null string pointer into "<>" */ #define stringStringInfo(s) (((s) == NULL) ? "<>" : (s)) @@ -41,7 +43,7 @@ static void ExplainOneQuery(Query *query, bool verbose, CommandDest dest); * */ void -ExplainQuery(Query *query, bool verbose, CommandDest dest) +ExplainQuery(Query *query, bool verbose, bool analyze, CommandDest dest) { List *rewritten; List *l; @@ -73,7 +75,7 @@ ExplainQuery(Query *query, bool verbose, CommandDest dest) /* Explain every plan */ foreach(l, rewritten) - ExplainOneQuery(lfirst(l), verbose, dest); + ExplainOneQuery(lfirst(l), verbose, analyze, dest); } /* @@ -82,11 +84,11 @@ ExplainQuery(Query *query, bool verbose, CommandDest dest) * */ static void -ExplainOneQuery(Query *query, bool verbose, CommandDest dest) +ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest) { - char *s; Plan *plan; ExplainState *es; + double totaltime = 0; /* planner will not cope with utility statements */ if (query->commandType == CMD_UTILITY) @@ -105,6 +107,34 @@ ExplainOneQuery(Query *query, bool verbose, CommandDest dest) if (plan == NULL) return; + /* Execute the plan for statistics if asked for */ + if (analyze) + { + struct timeval starttime; + struct timeval endtime; + + /* + * Set up the instrumentation for the top node. + * This will cascade during plan initialisation + */ + plan->instrument = InstrAlloc(); + + gettimeofday(&starttime, NULL); + ProcessQuery(query, plan, None); + CommandCounterIncrement(); + gettimeofday(&endtime, NULL); + + endtime.tv_sec -= starttime.tv_sec; + endtime.tv_usec -= starttime.tv_usec; + while (endtime.tv_usec < 0) + { + endtime.tv_usec += 1000000; + endtime.tv_sec--; + } + totaltime = (double) endtime.tv_sec + + (double) endtime.tv_usec / 1000000.0; + } + es = (ExplainState *) palloc(sizeof(ExplainState)); MemSet(es, 0, sizeof(ExplainState)); @@ -117,6 +147,8 @@ ExplainOneQuery(Query *query, bool verbose, CommandDest dest) if (es->printNodes) { + char *s; + s = nodeToString(plan); if (s) { @@ -127,12 +159,15 @@ ExplainOneQuery(Query *query, bool verbose, CommandDest dest) if (es->printCost) { - s = Explain_PlanToString(plan, es); - if (s) - { - elog(NOTICE, "QUERY PLAN:\n\n%s", s); - pfree(s); - } + StringInfo str; + + str = Explain_PlanToString(plan, es); + if (analyze) + appendStringInfo(str, "Total runtime: %.2f msec\n", + 1000.0 * totaltime); + elog(NOTICE, "QUERY PLAN:\n\n%s", str->data); + pfree(str->data); + pfree(str); } if (es->printNodes) @@ -292,6 +327,17 @@ explain_outNode(StringInfo str, Plan *plan, int indent, ExplainState *es) appendStringInfo(str, " (cost=%.2f..%.2f rows=%.0f width=%d)", plan->startup_cost, plan->total_cost, plan->plan_rows, plan->plan_width); + + if ( plan->instrument && plan->instrument->nloops > 0 ) + { + double nloops = plan->instrument->nloops; + + appendStringInfo(str, " (actual time=%.2f..%.2f rows=%.0f loops=%.0f)", + 1000.0 * plan->instrument->startup / nloops, + 1000.0 * plan->instrument->total / nloops, + plan->instrument->ntuples / nloops, + plan->instrument->nloops); + } } appendStringInfo(str, "\n"); @@ -393,14 +439,12 @@ explain_outNode(StringInfo str, Plan *plan, int indent, ExplainState *es) } } -static char * +static StringInfo Explain_PlanToString(Plan *plan, ExplainState *es) { - StringInfoData str; + StringInfo str = makeStringInfo(); - /* see stringinfo.h for an explanation of this maneuver */ - initStringInfo(&str); if (plan != NULL) - explain_outNode(&str, plan, 0, es); - return str.data; + explain_outNode(str, plan, 0, es); + return str; } diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index 7d57beb59f2895edcf4c84a68847d9d5ec9e2105..363ea342e9f46c4b541a90d4ba7e61af9c149338 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -4,7 +4,7 @@ # Makefile for executor # # IDENTIFICATION -# $Header: /cvsroot/pgsql/src/backend/executor/Makefile,v 1.16 2000/10/26 21:35:15 tgl Exp $ +# $Header: /cvsroot/pgsql/src/backend/executor/Makefile,v 1.17 2001/09/18 01:59:06 tgl Exp $ # #------------------------------------------------------------------------- @@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global OBJS = execAmi.o execFlatten.o execJunk.o execMain.o \ execProcnode.o execQual.o execScan.o execTuples.o \ - execUtils.o functions.o nodeAppend.o nodeAgg.o nodeHash.o \ + execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o nodeHash.o \ nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \ nodeNestloop.o nodeResult.o nodeSeqscan.o nodeSetOp.o nodeSort.o \ nodeUnique.o nodeLimit.o nodeGroup.o nodeSubplan.o \ diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 3c0e029fd3cac2a19c1c3bcea980df313c9eed04..6303613faa99742f67edeb217407aadbd86e5aca 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: execAmi.c,v 1.58 2001/03/22 06:16:12 momjian Exp $ + * $Id: execAmi.c,v 1.59 2001/09/18 01:59:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -29,6 +29,7 @@ #include "access/heapam.h" #include "catalog/heap.h" #include "executor/execdebug.h" +#include "executor/instrument.h" #include "executor/nodeAgg.h" #include "executor/nodeAppend.h" #include "executor/nodeGroup.h" @@ -260,6 +261,8 @@ ExecCloseR(Plan *node) void ExecReScan(Plan *node, ExprContext *exprCtxt, Plan *parent) { + if (node->instrument) + InstrEndLoop(node->instrument); if (node->chgParam != NULL) /* Wow! */ { diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 4061478427db2a8bce3a25f94e8f4483d1e4337c..6b1db391eeb3579652bfb7fe0335e94929c67430 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -27,7 +27,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.147 2001/09/17 00:29:10 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.148 2001/09/18 01:59:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -868,7 +868,7 @@ EndPlan(Plan *plan, EState *estate) /* * shut down the node-type-specific query processing */ - ExecEndNode(plan, plan); + ExecEndNode(plan, NULL); /* * destroy the executor "tuple" table. @@ -973,16 +973,15 @@ ExecutePlan(EState *estate, /* * Execute the plan and obtain a tuple */ - /* at the top level, the parent of a plan (2nd arg) is itself */ lnext: ; if (estate->es_useEvalPlan) { slot = EvalPlanQualNext(estate); if (TupIsNull(slot)) - slot = ExecProcNode(plan, plan); + slot = ExecProcNode(plan, NULL); } else - slot = ExecProcNode(plan, plan); + slot = ExecProcNode(plan, NULL); /* * if the tuple is null, then we assume there is nothing more to @@ -1758,7 +1757,7 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid) oldepq = (evalPlanQual *) epqstate->es_evalPlanQual; Assert(oldepq->rti != 0); /* stop execution */ - ExecEndNode(epq->plan, epq->plan); + ExecEndNode(epq->plan, NULL); ExecDropTupleTable(epqstate->es_tupleTable, true); epqstate->es_tupleTable = NULL; heap_freetuple(epqstate->es_evTuple[epq->rti - 1]); @@ -1853,7 +1852,7 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid) if (endNode) { /* stop execution */ - ExecEndNode(epq->plan, epq->plan); + ExecEndNode(epq->plan, NULL); ExecDropTupleTable(epqstate->es_tupleTable, true); epqstate->es_tupleTable = NULL; } @@ -1897,7 +1896,7 @@ EvalPlanQualNext(EState *estate) Assert(epq->rti != 0); lpqnext:; - slot = ExecProcNode(epq->plan, epq->plan); + slot = ExecProcNode(epq->plan, NULL); /* * No more tuples for this PQ. Continue previous one. @@ -1905,7 +1904,7 @@ lpqnext:; if (TupIsNull(slot)) { /* stop execution */ - ExecEndNode(epq->plan, epq->plan); + ExecEndNode(epq->plan, NULL); ExecDropTupleTable(epqstate->es_tupleTable, true); epqstate->es_tupleTable = NULL; heap_freetuple(epqstate->es_evTuple[epq->rti - 1]); @@ -1946,7 +1945,7 @@ EndEvalPlanQual(EState *estate) for (;;) { /* stop execution */ - ExecEndNode(epq->plan, epq->plan); + ExecEndNode(epq->plan, NULL); ExecDropTupleTable(epqstate->es_tupleTable, true); epqstate->es_tupleTable = NULL; if (epqstate->es_evTuple[epq->rti - 1] != NULL) diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index 79308ba4efb123cf73480411de56db55b1a3565c..0d66cd76c196cf56b8e01c5813c1e653f5a116cb 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -12,7 +12,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.26 2001/03/22 06:16:12 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.27 2001/09/18 01:59:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -78,6 +78,7 @@ #include "postgres.h" #include "executor/executor.h" +#include "executor/instrument.h" #include "executor/nodeAgg.h" #include "executor/nodeAppend.h" #include "executor/nodeGroup.h" @@ -123,6 +124,10 @@ ExecInitNode(Plan *node, EState *estate, Plan *parent) if (node == NULL) return FALSE; + /* Set up instrumentation for this node if the parent has it */ + if (!node->instrument && parent && parent->instrument) + node->instrument = InstrAlloc(); + foreach(subp, node->initPlan) { result = ExecInitSubPlan((SubPlan *) lfirst(subp), estate, node); @@ -132,7 +137,6 @@ ExecInitNode(Plan *node, EState *estate, Plan *parent) switch (nodeTag(node)) { - /* * control nodes */ @@ -218,6 +222,7 @@ ExecInitNode(Plan *node, EState *estate, Plan *parent) elog(ERROR, "ExecInitNode: node type %d unsupported", (int) nodeTag(node)); result = FALSE; + break; } if (result != FALSE) @@ -257,12 +262,14 @@ ExecProcNode(Plan *node, Plan *parent) if (node->chgParam != NULL) /* something changed */ ExecReScan(node, NULL, parent); /* let ReScan handle this */ + if (node->instrument) + InstrStartNode(node->instrument); + switch (nodeTag(node)) { - - /* - * control nodes - */ + /* + * control nodes + */ case T_Result: result = ExecResult((Result *) node); break; @@ -344,8 +351,12 @@ ExecProcNode(Plan *node, Plan *parent) elog(ERROR, "ExecProcNode: node type %d unsupported", (int) nodeTag(node)); result = NULL; + break; } + if (node->instrument) + InstrStopNode(node->instrument, !TupIsNull(result)); + return result; } @@ -357,7 +368,6 @@ ExecCountSlotsNode(Plan *node) switch (nodeTag(node)) { - /* * control nodes */ @@ -463,10 +473,9 @@ ExecEndNode(Plan *node, Plan *parent) switch (nodeTag(node)) { - - /* - * control nodes - */ + /* + * control nodes + */ case T_Result: ExecEndResult((Result *) node); break; @@ -549,6 +558,9 @@ ExecEndNode(Plan *node, Plan *parent) (int) nodeTag(node)); break; } + + if (node->instrument) + InstrEndLoop(node->instrument); } diff --git a/src/backend/executor/instrument.c b/src/backend/executor/instrument.c new file mode 100644 index 0000000000000000000000000000000000000000..753efbcee9a8300691b8172db2e555c366c7e388 --- /dev/null +++ b/src/backend/executor/instrument.c @@ -0,0 +1,122 @@ +/*------------------------------------------------------------------------- + * + * instrument.c + * functions for instrumentation of plan execution + * + * + * Copyright (c) 2001, PostgreSQL Global Development Group + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/instrument.c,v 1.1 2001/09/18 01:59:06 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <unistd.h> + +#include "executor/instrument.h" + + +/* Allocate new instrumentation structure */ +Instrumentation * +InstrAlloc(void) +{ + Instrumentation *instr = palloc( sizeof(Instrumentation) ); + + memset( instr, 0, sizeof(Instrumentation) ); + + return instr; +} + +/* Entry to a plan node */ +void +InstrStartNode(Instrumentation *instr) +{ + if (!instr) + return; + + if (instr->starttime.tv_sec != 0 || instr->starttime.tv_usec != 0) + elog(DEBUG, "InstrStartTimer called twice in a row"); + else + gettimeofday(&instr->starttime, NULL); +} + +/* Exit from a plan node */ +void +InstrStopNode(Instrumentation *instr, bool returnedTuple) +{ + struct timeval endtime; + + if (!instr) + return; + + if (instr->starttime.tv_sec == 0 && instr->starttime.tv_usec == 0) + { + elog(DEBUG, "InstrStopNode without start"); + return; + } + + gettimeofday(&endtime, NULL); + + instr->counter.tv_sec += endtime.tv_sec - instr->starttime.tv_sec; + instr->counter.tv_usec += endtime.tv_usec - instr->starttime.tv_usec; + + /* Normalize after each add to avoid overflow/underflow of tv_usec */ + while (instr->counter.tv_usec < 0) + { + instr->counter.tv_usec += 1000000; + instr->counter.tv_sec--; + } + while (instr->counter.tv_usec >= 1000000) + { + instr->counter.tv_usec -= 1000000; + instr->counter.tv_sec++; + } + + instr->starttime.tv_sec = 0; + instr->starttime.tv_usec = 0; + + /* Is this the first tuple of this cycle? */ + if (!instr->running) + { + instr->running = true; + instr->firsttuple = (double) instr->counter.tv_sec + + (double) instr->counter.tv_usec / 1000000.0; + } + + if (returnedTuple) + instr->tuplecount += 1; +} + +/* Finish a run cycle for a plan node */ +void +InstrEndLoop(Instrumentation *instr) +{ + double totaltime; + + if (!instr) + return; + + /* Skip if nothing has happened, or already shut down */ + if (!instr->running) + return; + + /* Accumulate statistics */ + totaltime = (double) instr->counter.tv_sec + + (double) instr->counter.tv_usec / 1000000.0; + + instr->startup += instr->firsttuple; + instr->total += totaltime; + instr->ntuples += instr->tuplecount; + instr->nloops += 1; + + /* Reset for next cycle (if any) */ + instr->running = false; + instr->starttime.tv_sec = 0; + instr->starttime.tv_usec = 0; + instr->counter.tv_sec = 0; + instr->counter.tv_usec = 0; + instr->firsttuple = 0; + instr->tuplecount = 0; +} diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index a8df4940ae497f149795d8f11f52d6f87223f30f..6719df94e7acf1586986bb4b77516d7a76e87bc2 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeSubplan.c,v 1.30 2001/03/22 03:59:29 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeSubplan.c,v 1.31 2001/09/18 01:59:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -73,7 +73,7 @@ ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext, bool *isNull) } Assert(pvar == NIL); - ExecReScan(plan, (ExprContext *) NULL, plan); + ExecReScan(plan, NULL, NULL); /* * For all sublink types except EXPR_SUBLINK, the result is boolean as @@ -96,9 +96,9 @@ ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext, bool *isNull) result = BoolGetDatum(subLinkType == ALL_SUBLINK); *isNull = false; - for (slot = ExecProcNode(plan, plan); + for (slot = ExecProcNode(plan, NULL); !TupIsNull(slot); - slot = ExecProcNode(plan, plan)) + slot = ExecProcNode(plan, NULL)) { HeapTuple tup = slot->val; TupleDesc tdesc = slot->ttc_tupleDescriptor; @@ -295,7 +295,7 @@ ExecInitSubPlan(SubPlan *node, EState *estate, Plan *parent) node->needShutdown = false; node->curTuple = NULL; - if (!ExecInitNode(node->plan, sp_estate, NULL)) + if (!ExecInitNode(node->plan, sp_estate, parent)) return false; node->needShutdown = true; /* now we need to shutdown the subplan */ @@ -359,11 +359,11 @@ ExecSetParamPlan(SubPlan *node, ExprContext *econtext) elog(ERROR, "ExecSetParamPlan: ANY/ALL subselect unsupported"); if (plan->chgParam != NULL) - ExecReScan(plan, (ExprContext *) NULL, plan); + ExecReScan(plan, NULL, NULL); - for (slot = ExecProcNode(plan, plan); + for (slot = ExecProcNode(plan, NULL); !TupIsNull(slot); - slot = ExecProcNode(plan, plan)) + slot = ExecProcNode(plan, NULL)) { HeapTuple tup = slot->val; TupleDesc tdesc = slot->ttc_tupleDescriptor; @@ -433,7 +433,7 @@ ExecSetParamPlan(SubPlan *node, ExprContext *econtext) if (plan->extParam == NULL) /* un-correlated ... */ { - ExecEndNode(plan, plan); + ExecEndNode(plan, NULL); node->needShutdown = false; } @@ -449,7 +449,7 @@ ExecEndSubPlan(SubPlan *node) { if (node->needShutdown) { - ExecEndNode(node->plan, node->plan); + ExecEndNode(node->plan, NULL); node->needShutdown = false; } if (node->curTuple) @@ -474,7 +474,7 @@ ExecReScanSetParamPlan(SubPlan *node, Plan *parent) /* * Don't actual re-scan: ExecSetParamPlan does re-scan if - * node->plan->chgParam is not NULL... ExecReScan (plan, NULL, plan); + * node->plan->chgParam is not NULL... ExecReScan (plan, NULL, NULL); */ /* diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c index af2b878165b4c2ae77b4acbe56fb9fa56ba7994b..8c178ad0aa4ebdbeb7edf53bec785441be4b08ee 100644 --- a/src/backend/executor/nodeSubqueryscan.c +++ b/src/backend/executor/nodeSubqueryscan.c @@ -12,7 +12,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeSubqueryscan.c,v 1.9 2001/05/27 20:42:20 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeSubqueryscan.c,v 1.10 2001/09/18 01:59:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -72,7 +72,7 @@ SubqueryNext(SubqueryScan *node) */ subquerystate->sss_SubEState->es_direction = direction; - slot = ExecProcNode(node->subplan, node->subplan); + slot = ExecProcNode(node->subplan, (Plan *) node); subquerystate->csstate.css_ScanTupleSlot = slot; @@ -159,7 +159,7 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, Plan *parent) ExecCreateTupleTable(ExecCountSlotsNode(node->subplan) + 10); sp_estate->es_snapshot = estate->es_snapshot; - if (!ExecInitNode(node->subplan, sp_estate, NULL)) + if (!ExecInitNode(node->subplan, sp_estate, (Plan *) node)) return false; subquerystate->csstate.css_ScanTupleSlot = NULL; @@ -214,7 +214,7 @@ ExecEndSubqueryScan(SubqueryScan *node) /* * close down subquery */ - ExecEndNode(node->subplan, node->subplan); + ExecEndNode(node->subplan, (Plan *) node); /* * clean up subquery's tuple table @@ -256,7 +256,7 @@ ExecSubqueryReScan(SubqueryScan *node, ExprContext *exprCtxt, Plan *parent) * first ExecProcNode. */ if (node->subplan->chgParam == NULL) - ExecReScan(node->subplan, NULL, node->subplan); + ExecReScan(node->subplan, NULL, (Plan *) node); subquerystate->csstate.css_ScanTupleSlot = NULL; } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index cf9ad2e983c5967e3984ff11b4b277806547e115..1003bcf35d5da2135128ac774e0c5ddda0821518 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.155 2001/08/26 16:55:59 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.156 2001/09/18 01:59:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2262,6 +2262,7 @@ _copyExplainStmt(ExplainStmt *from) Node_Copy(from, newnode, query); newnode->verbose = from->verbose; + newnode->analyze = from->analyze; return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 9dce1f61b46111754d4b03cd5b4dc06b10578a08..437cd62ebdf162aa747a038b0dcbf2e5a2f66ab7 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -20,7 +20,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.103 2001/08/26 16:55:59 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.104 2001/09/18 01:59:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1135,6 +1135,8 @@ _equalExplainStmt(ExplainStmt *a, ExplainStmt *b) return false; if (a->verbose != b->verbose) return false; + if (a->analyze != b->analyze) + return false; return true; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index f440e239a868718c8cf022fcc0542ef7865c2440..c1e331cf81918088aa1ba9f0d7bb5b835de550e2 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.250 2001/09/06 04:57:28 ishii Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.251 2001/09/18 01:59:06 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -3168,6 +3168,7 @@ opt_name_list: '(' name_list ')' { $$ = $2; } * * QUERY: * EXPLAIN query + * EXPLAIN ANALYZE query * *****************************************************************************/ @@ -3175,9 +3176,18 @@ ExplainStmt: EXPLAIN opt_verbose OptimizableStmt { ExplainStmt *n = makeNode(ExplainStmt); n->verbose = $2; + n->analyze = FALSE; n->query = (Query*)$3; $$ = (Node *)n; } + | EXPLAIN analyze_keyword opt_verbose OptimizableStmt + { + ExplainStmt *n = makeNode(ExplainStmt); + n->verbose = $3; + n->analyze = TRUE; + n->query = (Query*)$4; + $$ = (Node *)n; + } ; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 18cf913470ca1a4e947f35502195137af186f74a..957c9199af1dae192bffd640bbebdde445a957d5 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.117 2001/09/08 01:10:20 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.118 2001/09/18 01:59:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -713,7 +713,7 @@ ProcessUtility(Node *parsetree, set_ps_display(commandTag = "EXPLAIN"); - ExplainQuery(stmt->query, stmt->verbose, dest); + ExplainQuery(stmt->query, stmt->verbose, stmt->analyze, dest); } break; diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 25bb0ecc490575c890bd40faa2e67d52e605f2c1..d40bc896ac5cac3dcb5c1558ba82816c81968bb1 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * - * $Id: explain.h,v 1.11 2001/01/24 19:43:23 momjian Exp $ + * $Id: explain.h,v 1.12 2001/09/18 01:59:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,6 +16,6 @@ #include "nodes/parsenodes.h" #include "tcop/dest.h" -extern void ExplainQuery(Query *query, bool verbose, CommandDest dest); +extern void ExplainQuery(Query *query, bool verbose, bool analyze, CommandDest dest); #endif /* EXPLAIN_H */ diff --git a/src/include/executor/instrument.h b/src/include/executor/instrument.h new file mode 100644 index 0000000000000000000000000000000000000000..ca6aa4208abbac7cc9cb68b297f9906e00db9b53 --- /dev/null +++ b/src/include/executor/instrument.h @@ -0,0 +1,39 @@ +/*------------------------------------------------------------------------- + * + * instrument.h + * definitions for run-time statistics collection + * + * + * Copyright (c) 2001, PostgreSQL Global Development Group + * + * $Id: instrument.h,v 1.1 2001/09/18 01:59:06 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef INSTRUMENT_H +#define INSTRUMENT_H + +#include <sys/time.h> + + +typedef struct Instrumentation +{ + /* Info about current plan cycle: */ + bool running; /* TRUE if we've completed first tuple */ + struct timeval starttime; /* Start time of current iteration of node */ + struct timeval counter; /* Accumulates runtime for this node */ + double firsttuple; /* Time for first tuple of this cycle */ + double tuplecount; /* Tuples so far this cycle */ + /* Accumulated statistics across all completed cycles: */ + double startup; /* Total startup time (in seconds) */ + double total; /* Total total time (in seconds) */ + double ntuples; /* Total tuples produced */ + double nloops; /* # of run cycles for this node */ +} Instrumentation; + +extern Instrumentation *InstrAlloc(void); +extern void InstrStartNode(Instrumentation *instr); +extern void InstrStopNode(Instrumentation *instr, bool returnedTuple); +extern void InstrEndLoop(Instrumentation *instr); + +#endif /* INSTRUMENT_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 315ad113ee766c5a1e53a5575f084e1aa2a47f5a..c5a68ef7c90bdf6da48bfd43da25908b1d1ed7a5 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.143 2001/08/26 16:56:02 tgl Exp $ + * $Id: parsenodes.h,v 1.144 2001/09/18 01:59:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -709,6 +709,7 @@ typedef struct ExplainStmt NodeTag type; Query *query; /* the query */ bool verbose; /* print plan info */ + bool analyze; /* get statistics by executing plan */ } ExplainStmt; /* ---------------------- diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 1ee5e93cb492d75a6ef8182eb8ad5ca5ec07d75b..eef1678432743286579fe06fc9a713f63812132d 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: plannodes.h,v 1.49 2001/03/22 04:00:52 momjian Exp $ + * $Id: plannodes.h,v 1.50 2001/09/18 01:59:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -81,9 +81,21 @@ typedef struct Plan double plan_rows; /* number of rows plan is expected to emit */ int plan_width; /* average row width in bytes */ + /* + * execution state data. Having Plan point to this, rather than the + * other way round, is 100% bogus. + */ EState *state; /* at execution time, state's of * individual nodes point to one EState * for the whole top-level plan */ + + struct Instrumentation *instrument; /* Optional runtime stats for this + * plan node */ + + /* + * Common structural data for all Plan types. XXX chgParam is runtime + * data and should be in the EState, not here. + */ List *targetlist; List *qual; /* implicitly-ANDed qual conditions */ struct Plan *lefttree;