Hi,

-- commit 9d2d9728b8d546434aade4f9667a59666588edd6
Author: Michael Paquier <mich...@paquier.xyz>
Date:   Thu Jan 26 12:23:16 2023 +0900

Make auto_explain print the query identifier in verbose mode
..(snip)..
While looking at the area, I have noticed that more consolidation
between EXPLAIN and auto_explain would be in order for the logging of
the plan duration and the buffer usage.  This refactoring is left as a
future change.

I'm working on this now.
Attached a PoC patch which enables auto_explain to log plan duration and buffer usage on planning phase.
Last 3 lines are added by this patch:

 ```
=# set auto_explain.log_min_duration = 0;
=# set auto_explain.log_verbose = on;
=# set auto_explain.log_analyze = on;
=# select * from pg_class;

LOG:  00000: duration: 6.774 ms  plan:
        Query Text: select * from pg_class;
Seq Scan on pg_catalog.pg_class (cost=0.00..18.12 rows=412 width=273) (actual time=0.009..0.231 rows=412 loops=1) Output: oid, relname, relnamespace, reltype, reloftype, relowner, relam, relfilenode, reltablespace, relpages, reltuples, relallvisible, reltoastrelid, relhasindex, relisshared, relpersistence, relkind, relnatts, relchecks, relhasrules, relhastriggers, relhassubclass, relrowsecurity, relforcerowsecurity, relispopulated, relreplident, relispartition, relrewrite, relfrozenxid, relminmxid, relacl, reloptions, relpartbound
          Buffers: shared hit=14
        Query Identifier: 8034096446570639838
        Planning
          Buffers: shared hit=120
        Planning Time: 3.908 ms
 ```

It adds a planner hook to track the plan duration and buffer usage for planning.
I'm considering the following points and any comments are welcome:

- Plan duration and buffer usage are saved on PlannedStmt. As far as I referred totaltime in QueryDesc, adding elements for extensions is not always prohibited, but I'm wondering it's ok to add them in this case. - Just as pg_stat_statements made it possible to add planner information in v13, it may be useful for auto_explain to log planner phase information, especially plan duration. However, I am not sure to what extent information about the buffers used in the plan phase would be useful. - Plan duration and buffer usage may differ from the output of EXPLAIN command since EXPLAIN command includes pg_plan_query(), but planner hook doesn’t.
pg_plan_query() do things for  log_planner_stats, debugging and tracing.
- (Future works) Log output concerning buffers should be toggled on/off by auto_explain.log_buffers. Log output concerning planning should be toggled on/off by new GUC something like auto_explain.track_planning.


What do you think?

--
Regards,

--
Atsushi Torikoshi
NTT DATA CORPORATION
From a0997f139cd964ca0368fe3650e5ad1ce1f5e16e Mon Sep 17 00:00:00 2001
From: Atsushi Torikoshi <torikos...@oss.nttdata.com>
Date: Tue, 7 Feb 2023 19:49:45 +0900
Subject: [PATCH v1] [PoC] Enable auto_explain to track planning time and
 buffer usage

---
 contrib/auto_explain/auto_explain.c   | 82 ++++++++++++++++++++++++++-
 src/backend/commands/explain.c        | 36 ++++++------
 src/backend/nodes/gen_node_support.pl |  4 ++
 src/backend/optimizer/plan/planner.c  |  2 +
 src/include/commands/explain.h        |  4 +-
 src/include/nodes/plannodes.h         |  6 ++
 6 files changed, 114 insertions(+), 20 deletions(-)

diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index c3ac27ae99..ea5c2949d0 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -20,6 +20,7 @@
 #include "executor/instrument.h"
 #include "jit/jit.h"
 #include "nodes/params.h"
+#include "optimizer/planner.h"
 #include "utils/guc.h"
 
 PG_MODULE_MAGIC;
@@ -64,20 +65,28 @@ static const struct config_enum_entry loglevel_options[] = {
 /* Current nesting depth of ExecutorRun calls */
 static int	nesting_level = 0;
 
+/* Current nesting depth of planner calls */
+static int	plan_nesting_level = 0;
+
 /* Is the current top-level query to be sampled? */
 static bool current_query_sampled = false;
 
 #define auto_explain_enabled() \
 	(auto_explain_log_min_duration >= 0 && \
-	 (nesting_level == 0 || auto_explain_log_nested_statements) && \
+	 ((nesting_level == 0 || plan_nesting_level == 0) || \
+	   auto_explain_log_nested_statements) && \
 	 current_query_sampled)
 
 /* Saved hook values in case of unload */
+static planner_hook_type prev_planner_hook = NULL;
 static ExecutorStart_hook_type prev_ExecutorStart = NULL;
 static ExecutorRun_hook_type prev_ExecutorRun = NULL;
 static ExecutorFinish_hook_type prev_ExecutorFinish = NULL;
 static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
 
+static PlannedStmt *explain_Planner(Query *parse, const char *query_string,
+								int cursorOptions,
+								 ParamListInfo boundParams);
 static void explain_ExecutorStart(QueryDesc *queryDesc, int eflags);
 static void explain_ExecutorRun(QueryDesc *queryDesc,
 								ScanDirection direction,
@@ -245,6 +254,8 @@ _PG_init(void)
 	MarkGUCPrefixReserved("auto_explain");
 
 	/* Install hooks. */
+	prev_planner_hook = planner_hook;
+	planner_hook = explain_Planner;
 	prev_ExecutorStart = ExecutorStart_hook;
 	ExecutorStart_hook = explain_ExecutorStart;
 	prev_ExecutorRun = ExecutorRun_hook;
@@ -255,6 +266,72 @@ _PG_init(void)
 	ExecutorEnd_hook = explain_ExecutorEnd;
 }
 
+/*
+ * Planner hook: track nesting level and start up logging if needed
+ */
+static PlannedStmt *
+explain_Planner(Query *parse, const char *query_string, int cursorOptions, ParamListInfo boundParams)
+{
+	PlannedStmt *result;
+
+	if (plan_nesting_level == 0)
+	{
+		if (auto_explain_log_min_duration >= 0 && !IsParallelWorker())
+			current_query_sampled = (pg_prng_double(&pg_global_prng_state) < auto_explain_sample_rate);
+		else
+			current_query_sampled = false;
+	}
+
+	if (auto_explain_enabled())
+	{
+	// TODO: Log output concerning buffers should be toggled on/off by auto_explain.log_buffers.
+	// TODO: Log output concerning planning should be toggled on/off by auto_explain.track_planning.
+
+		instr_time	start;
+		instr_time	duration;
+		BufferUsage bufusage_start;
+
+		/* We need to track buffer usage as the planner can access them. */
+		bufusage_start = pgBufferUsage;
+
+		INSTR_TIME_SET_CURRENT(start);
+
+		plan_nesting_level++;
+		PG_TRY();
+		{
+			if (prev_planner_hook)
+				result = prev_planner_hook(parse, query_string, cursorOptions,
+										   boundParams);
+			else
+				result = standard_planner(parse, query_string, cursorOptions,
+										  boundParams);
+		}
+		PG_FINALLY();
+		{
+			plan_nesting_level--;
+		}
+		PG_END_TRY();
+
+		INSTR_TIME_SET_CURRENT(duration);
+		INSTR_TIME_SUBTRACT(duration, start);
+
+		result->plantime = duration;
+		/* calc differences of buffer counters and save it */
+		BufferUsageAccumDiff(&result->bufusage, &pgBufferUsage, &bufusage_start);
+	}
+	else
+	{
+		if (prev_planner_hook)
+			result = prev_planner_hook(parse, query_string, cursorOptions,
+									   boundParams);
+		else
+			result = standard_planner(parse, query_string, cursorOptions,
+									  boundParams);
+	}
+
+	return result;
+}
+
 /*
  * ExecutorStart hook: start up logging if needed
  */
@@ -402,7 +479,8 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
 			ExplainBeginOutput(es);
 			ExplainQueryText(es, queryDesc);
 			ExplainQueryParameters(es, queryDesc->params, auto_explain_log_parameter_max_length);
-			ExplainPrintPlan(es, queryDesc);
+			ExplainPrintPlan(es, queryDesc, &queryDesc->plannedstmt->plantime,
+							 &queryDesc->plannedstmt->bufusage);
 			if (es->analyze && auto_explain_log_triggers)
 				ExplainPrintTriggers(es, queryDesc);
 			if (es->costs)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a0311ce9dc..f92c7ebb98 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -602,22 +602,7 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
 	ExplainOpenGroup("Query", NULL, true, es);
 
 	/* Create textual dump of plan tree */
-	ExplainPrintPlan(es, queryDesc);
-
-	/* Show buffer usage in planning */
-	if (bufusage)
-	{
-		ExplainOpenGroup("Planning", "Planning", true, es);
-		show_buffer_usage(es, bufusage, true);
-		ExplainCloseGroup("Planning", "Planning", true, es);
-	}
-
-	if (es->summary && planduration)
-	{
-		double		plantime = INSTR_TIME_GET_DOUBLE(*planduration);
-
-		ExplainPropertyFloat("Planning Time", "ms", 1000.0 * plantime, 3, es);
-	}
+	ExplainPrintPlan(es, queryDesc, planduration, bufusage);
 
 	/* Print info about runtime of triggers */
 	if (es->analyze)
@@ -738,7 +723,9 @@ ExplainPrintSettings(ExplainState *es)
  * NB: will not work on utility statements
  */
 void
-ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
+ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc,
+				 const instr_time *planduration,
+				 const BufferUsage *bufusage)
 {
 	Bitmapset  *rels_used = NULL;
 	PlanState  *ps;
@@ -791,6 +778,21 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
 		ExplainPropertyInteger("Query Identifier", NULL, (int64)
 							   queryDesc->plannedstmt->queryId, es);
 	}
+
+	/* Show buffer usage in planning */
+	if (bufusage)
+	{
+		ExplainOpenGroup("Planning", "Planning", true, es);
+		show_buffer_usage(es, bufusage, true);
+		ExplainCloseGroup("Planning", "Planning", true, es);
+	}
+
+	if (es->summary && planduration)
+	{
+		double		plantime = INSTR_TIME_GET_DOUBLE(*planduration);
+
+		ExplainPropertyFloat("Planning Time", "ms", 1000.0 * plantime, 3, es);
+	}
 }
 
 /*
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index b3c1ead496..65d8721865 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -759,6 +759,8 @@ _equal${n}(const $n *a, const $n *b)
 			print $eff "\tCOMPARE_BITMAPSET_FIELD($f);\n"
 			  unless $equal_ignore;
 		}
+		# temporarily change for PoC
+		elsif ($t eq 'instr_time' || $t eq 'BufferUsage') { }
 		elsif ($t eq 'int' && $f =~ 'location$')
 		{
 			print $cff "\tCOPY_LOCATION_FIELD($f);\n"    unless $copy_ignore;
@@ -1073,6 +1075,8 @@ _read${n}(void)
 			print $off "\tWRITE_BITMAPSET_FIELD($f);\n";
 			print $rff "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read;
 		}
+		# temporalily change for PoC
+		elsif ($t eq 'instr_time' || $t eq 'BufferUsage') { }
 		elsif (elem $t, @enum_types)
 		{
 			print $off "\tWRITE_ENUM_FIELD($f, $t);\n";
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 05f44faf6e..5095da68f6 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -564,6 +564,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 		if (jit_tuple_deforming)
 			result->jitFlags |= PGJIT_DEFORM;
 	}
+	memset(&result->bufusage, 0, sizeof(BufferUsage));
+	memset(&result->plantime, 0, sizeof(instr_time));
 
 	if (glob->partition_directory != NULL)
 		DestroyPartitionDirectory(glob->partition_directory);
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 7c1071ddd1..690bc7bece 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -93,7 +93,9 @@ extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into,
 						   const instr_time *planduration,
 						   const BufferUsage *bufusage);
 
-extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc);
+extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc,
+							 const instr_time *planduration,
+							 const BufferUsage *bufusage);
 extern void ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc);
 
 extern void ExplainPrintJITSummary(ExplainState *es, QueryDesc *queryDesc);
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index c1234fcf36..e0076a2042 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -17,11 +17,13 @@
 #include "access/sdir.h"
 #include "access/stratnum.h"
 #include "common/relpath.h"
+#include "executor/instrument.h"
 #include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/lockoptions.h"
 #include "nodes/parsenodes.h"
 #include "nodes/primnodes.h"
+#include "portability/instr_time.h"
 
 
 /* ----------------------------------------------------------------
@@ -101,6 +103,10 @@ typedef struct PlannedStmt
 	/* statement location in source string (copied from Query) */
 	int			stmt_location;	/* start location, or -1 if unknown */
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
+
+	/* These are never used by the core system, but plugins can change them */
+	instr_time	plantime;		/* planning time */
+	BufferUsage bufusage;		/* buffer usage for planning */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
-- 
2.25.1

Reply via email to