]> granicus.if.org Git - postgresql/commitdiff
EXPLAIN ANALYZE feature to measure and show actual runtimes and tuple
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 18 Sep 2001 01:59:07 +0000 (01:59 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 18 Sep 2001 01:59:07 +0000 (01:59 +0000)
counts alongside the planner's estimates.  By Martijn van Oosterhout,
with some further work by Tom Lane.

18 files changed:
doc/src/sgml/perform.sgml
doc/src/sgml/ref/explain.sgml
src/backend/commands/explain.c
src/backend/executor/Makefile
src/backend/executor/execAmi.c
src/backend/executor/execMain.c
src/backend/executor/execProcnode.c
src/backend/executor/instrument.c [new file with mode: 0644]
src/backend/executor/nodeSubplan.c
src/backend/executor/nodeSubqueryscan.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/tcop/utility.c
src/include/commands/explain.h
src/include/executor/instrument.h [new file with mode: 0644]
src/include/nodes/parsenodes.h
src/include/nodes/plannodes.h

index 6e82d82aad169e96444344df40333a942b70fc08..6628c9f4925b269685c080edd9901a6008980cc0 100644 (file)
@@ -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 &lt; 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 &lt; 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 &lt; 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 &lt; 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)
   -&gt;  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)
   -&gt;  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 &lt; 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)
   -&gt;  Seq Scan on tenk2 t2
                (cost=0.00..333.00 rows=10000 width=148)
-  -&gt;  Hash  (cost=173.32..173.32 rows=47 width=148)
+  -&gt;  Hash  (cost=181.09..181.09 rows=49 width=148)
         -&gt;  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 &lt; 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)
+  -&gt;  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)
+  -&gt;  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,
index 7bccea72ad772eaa1bc0350811ceb4a533965cec..b24863529b3eb9ebf3242911e632451d46fb5729 100644 (file)
@@ -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>
index 672ec54cb029d283447ff9cbcd0c0a6ee64b4ffa..91282ffc3f60465688c25f50452c3e698968749c 100644 (file)
@@ -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;
 }
index 7d57beb59f2895edcf4c84a68847d9d5ec9e2105..363ea342e9f46c4b541a90d4ba7e61af9c149338 100644 (file)
@@ -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 \
index 3c0e029fd3cac2a19c1c3bcea980df313c9eed04..6303613faa99742f67edeb217407aadbd86e5aca 100644 (file)
@@ -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! */
        {
index 4061478427db2a8bce3a25f94e8f4483d1e4337c..6b1db391eeb3579652bfb7fe0335e94929c67430 100644 (file)
@@ -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)
index 79308ba4efb123cf73480411de56db55b1a3565c..0d66cd76c196cf56b8e01c5813c1e653f5a116cb 100644 (file)
@@ -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 (file)
index 0000000..753efbc
--- /dev/null
@@ -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;
+}
index a8df4940ae497f149795d8f11f52d6f87223f30f..6719df94e7acf1586986bb4b77516d7a76e87bc2 100644 (file)
@@ -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);
         */
 
        /*
index af2b878165b4c2ae77b4acbe56fb9fa56ba7994b..8c178ad0aa4ebdbeb7edf53bec785441be4b08ee 100644 (file)
@@ -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;
 }
index cf9ad2e983c5967e3984ff11b4b277806547e115..1003bcf35d5da2135128ac774e0c5ddda0821518 100644 (file)
@@ -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;
 }
index 9dce1f61b46111754d4b03cd5b4dc06b10578a08..437cd62ebdf162aa747a038b0dcbf2e5a2f66ab7 100644 (file)
@@ -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;
 }
index f440e239a868718c8cf022fcc0542ef7865c2440..c1e331cf81918088aa1ba9f0d7bb5b835de550e2 100644 (file)
@@ -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;
+                               }
                ;
 
 
index 18cf913470ca1a4e947f35502195137af186f74a..957c9199af1dae192bffd640bbebdde445a957d5 100644 (file)
@@ -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;
 
index 25bb0ecc490575c890bd40faa2e67d52e605f2c1..d40bc896ac5cac3dcb5c1558ba82816c81968bb1 100644 (file)
@@ -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 (file)
index 0000000..ca6aa42
--- /dev/null
@@ -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 */
index 315ad113ee766c5a1e53a5575f084e1aa2a47f5a..c5a68ef7c90bdf6da48bfd43da25908b1d1ed7a5 100644 (file)
@@ -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;
 
 /* ----------------------
index 1ee5e93cb492d75a6ef8182eb8ad5ca5ec07d75b..eef1678432743286579fe06fc9a713f63812132d 100644 (file)
@@ -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;