Re: Logging query parmeters in auto_explain

Поиск
Список
Период
Сортировка
От Dagfinn Ilmari Mannsåker
Тема Re: Logging query parmeters in auto_explain
Дата
Msg-id 87iloml5a5.fsf@wibble.ilmari.org
обсуждение исходный текст
Ответ на Re: Logging query parmeters in auto_explain  (Michael Paquier <michael@paquier.xyz>)
Ответы Re: Logging query parmeters in auto_explain  (Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>)
Список pgsql-hackers
Michael Paquier <michael@paquier.xyz> writes:

> On Thu, Jun 09, 2022 at 11:55:11PM +0100, Dagfinn Ilmari Mannsåker wrote:
>> Done (and more tests added), v3 attached.
>
> One thing I am wondering is if we'd better mention errdetail_params()
> at the top of the initial check done in ExplainQueryParameters().
> That's a minor issue, though.
>
> +sub query_log
> +{
> Perhaps a short description at the top of this routine to explain it
> is worth it?

Done. I also moved the function to the bottom of the file, to avoid
distracting from the actual test queries.

> The test still does a set of like() and unlike() after running each
> query when the parameter updates are done.  One thing I would have
> played with is to group the set of logs expected or not expected as
> parameters of the centralized routine, but that would reduce the
> customization of the test names, so at the end the approach you have
> taken for query_log() looks good to me.

I did consider passing the tests as a data structure to the function,
but that would amount to specifying exactly the same things but as a
data structure, and then calling the appropriate function by reference,
which just makes things more cluttered.

If we were using TAP subtests, it might make a sense to have the
function run each set of related tests in a subtest, rather than having
multiple subtest calls at the top level, but we don't, so it doesn't.

> +$node->stop('fast');
> There is no need for that.  The END block of Cluster.pm does that
> already.

Ah, I was not aware of that. The call was there in the original version,
so I had just left it in. Removed.

v4 patch attached.

- ilmari

From 1c504beecdf2a0e64a7bbd1d0e07b6b33fdc533b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dagfinn=20Ilmari=20Manns=C3=A5ker?= <ilmari@ilmari.org>
Date: Tue, 31 May 2022 21:12:12 +0100
Subject: [PATCH v4] Log query parameters in auto_explain

Add an auto_explain.log_parameter_max_length config setting, similar
to the corresponding core setting, that controls the inclusion of
query parameters in the logged explain output.

Also adjust the tests to only look at the relevant parts of the log
for each query, and tho reset the GUCs after each test.
---
 contrib/auto_explain/auto_explain.c        |  15 +++
 contrib/auto_explain/t/001_auto_explain.pl | 134 ++++++++++++++++++---
 doc/src/sgml/auto-explain.sgml             |  19 +++
 src/backend/commands/explain.c             |  22 ++++
 src/include/commands/explain.h             |   1 +
 5 files changed, 177 insertions(+), 14 deletions(-)

diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index c9a0d947c8..1ba7536879 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -19,12 +19,14 @@
 #include "common/pg_prng.h"
 #include "executor/instrument.h"
 #include "jit/jit.h"
+#include "nodes/params.h"
 #include "utils/guc.h"
 
 PG_MODULE_MAGIC;
 
 /* GUC variables */
 static int    auto_explain_log_min_duration = -1; /* msec or -1 */
+static int    auto_explain_log_parameter_max_length = -1; /* bytes or -1 */
 static bool auto_explain_log_analyze = false;
 static bool auto_explain_log_verbose = false;
 static bool auto_explain_log_buffers = false;
@@ -105,6 +107,18 @@ _PG_init(void)
                             NULL,
                             NULL);
 
+    DefineCustomIntVariable("auto_explain.log_parameter_max_length",
+                            "Sets the maximum length of query parameters to log.",
+                            "Zero logs no query parameters, -1 logs them in full.",
+                            &auto_explain_log_parameter_max_length,
+                            -1,
+                            -1, INT_MAX,
+                            PGC_SUSET,
+                            GUC_UNIT_BYTE,
+                            NULL,
+                            NULL,
+                            NULL);
+
     DefineCustomBoolVariable("auto_explain.log_analyze",
                              "Use EXPLAIN ANALYZE for plan logging.",
                              NULL,
@@ -389,6 +403,7 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
 
             ExplainBeginOutput(es);
             ExplainQueryText(es, queryDesc);
+            ExplainQueryParameters(es, queryDesc->params, auto_explain_log_parameter_max_length);
             ExplainPrintPlan(es, queryDesc);
             if (es->analyze && auto_explain_log_triggers)
                 ExplainPrintTriggers(es, queryDesc);
diff --git a/contrib/auto_explain/t/001_auto_explain.pl b/contrib/auto_explain/t/001_auto_explain.pl
index 82e4d9d15c..2d04147090 100644
--- a/contrib/auto_explain/t/001_auto_explain.pl
+++ b/contrib/auto_explain/t/001_auto_explain.pl
@@ -17,41 +17,147 @@
 $node->start;
 
 # run a couple of queries
-$node->safe_psql("postgres", "SELECT * FROM pg_class;");
-$node->safe_psql("postgres",
-    "SELECT * FROM pg_proc WHERE proname = 'int4pl';");
+my $log_contents = query_log("SELECT * FROM pg_class;");
 
-# emit some json too
-$node->append_conf('postgresql.conf', "auto_explain.log_format = json");
-$node->reload;
-$node->safe_psql("postgres", "SELECT * FROM pg_proc;");
-$node->safe_psql("postgres",
-    "SELECT * FROM pg_class WHERE relname = 'pg_class';");
+like(
+    $log_contents,
+    qr/Query Text: SELECT \* FROM pg_class;/,
+    "query text logged, text mode");
 
-$node->stop('fast');
-
-my $log = $node->logfile();
-
-my $log_contents = slurp_file($log);
+unlike(
+    $log_contents,
+    qr/Query Parameters:/,
+    "no query parameters logged when none, text mode");
 
 like(
     $log_contents,
     qr/Seq Scan on pg_class/,
     "sequential scan logged, text mode");
 
+# with parameters
+$log_contents = query_log(
+    q{PREPARE get_proc(name) AS SELECT * FROM pg_proc WHERE proname = $1; EXECUTE get_proc('int4pl');}
+);
+
+like(
+    $log_contents,
+    qr/Query Text: PREPARE get_proc\(name\) AS SELECT \* FROM pg_proc WHERE proname = \$1;/,
+    "prepared query text logged, text mode");
+
+like(
+    $log_contents,
+    qr/Query Parameters: \$1 = 'int4pl'/,
+    "query parameters logged, text mode");
+
 like(
     $log_contents,
     qr/Index Scan using pg_proc_proname_args_nsp_index on pg_proc/,
     "index scan logged, text mode");
 
+# check that logged parameters can be truncated
+$log_contents = query_log(
+    q{PREPARE get_type(name) AS SELECT * FROM pg_type WHERE typname = $1; EXECUTE get_type('float8');},
+    { "auto_explain.log_parameter_max_length" => 3 });
+
+like(
+    $log_contents,
+    qr/Query Text: PREPARE get_type\(name\) AS SELECT \* FROM pg_type WHERE typname = \$1;/,
+    "prepared query text logged, text mode");
+
+like(
+    $log_contents,
+    qr/Query Parameters: \$1 = 'flo\.\.\.'/,
+    "query parameters truncated, text mode");
+
+# check that parameter logging can be disabled
+$log_contents = query_log(
+    q{PREPARE get_type(name) AS SELECT * FROM pg_type WHERE typname = $1; EXECUTE get_type('float8');},
+    { "auto_explain.log_parameter_max_length" => 0 });
+
+like(
+    $log_contents,
+    qr/Query Text: PREPARE get_type\(name\) AS SELECT \* FROM pg_type WHERE typname = \$1;/,
+    "prepared query text logged, text mode");
+
+unlike(
+    $log_contents,
+    qr/Query Parameters:/,
+    "query parameters not logged when disabled, text mode");
+
+# emit some json too
+$log_contents = query_log("SELECT * FROM pg_proc;",
+    { "auto_explain.log_format" => "json" });
+
+like(
+    $log_contents,
+    qr/"Query Text": "SELECT \* FROM pg_proc;"/,
+    "query text logged, json mode");
+
+unlike(
+    $log_contents,
+    qr/"Query Parameters":/,
+    "query parameters not logged when none, json mode");
+
 like(
     $log_contents,
     qr/"Node Type": "Seq Scan"[^}]*"Relation Name": "pg_proc"/s,
     "sequential scan logged, json mode");
 
+# and with parameters
+$log_contents = query_log(
+    q{PREPARE get_class(name) AS SELECT * FROM pg_class WHERE relname = $1; EXECUTE get_class('pg_class');},
+    { "auto_explain.log_format" => "json" });
+
+like(
+    $log_contents,
+    qr/"Query Text": "PREPARE get_class\(name\) AS SELECT \* FROM pg_class WHERE relname = \$1;"/,
+    "prepared query text logged, json mode");
+
+like(
+    $log_contents,
+    qr/"Query Parameters": "\$1 = 'pg_class'"/,
+    "query parameters logged, json mode");
+
 like(
     $log_contents,
     qr/"Node Type": "Index Scan"[^}]*"Index Name": "pg_class_relname_nsp_index"/s,
     "index scan logged, json mode");
 
 done_testing();
+
+# Runs the specified query and returns the emitted server log.
+# If any parameters are specified, these are set in postgresql.conf,
+# and reset after the query is run.
+sub query_log
+{
+    my ($sql, $params) = @_;
+    $params ||= {};
+
+    if (keys %$params)
+    {
+        for my $key (keys %$params)
+        {
+            $node->append_conf('postgresql.conf', "$key = $params->{$key}\n");
+        }
+        $node->reload;
+    }
+
+    my $log    = $node->logfile();
+    my $offset = -s $log;
+
+    $node->safe_psql("postgres", $sql);
+
+    my $log_contents = slurp_file($log, $offset);
+
+    if (keys %$params)
+    {
+        for my $key (keys %$params)
+        {
+            $node->adjust_conf('postgresql.conf', $key, undef);
+        }
+        $node->reload;
+    }
+
+    return $log_contents;
+}
+
diff --git a/doc/src/sgml/auto-explain.sgml b/doc/src/sgml/auto-explain.sgml
index 30e35a714a..a033c9c685 100644
--- a/doc/src/sgml/auto-explain.sgml
+++ b/doc/src/sgml/auto-explain.sgml
@@ -63,6 +63,25 @@
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term>
+     <varname>auto_explain.log_parameter_max_length</varname> (<type>integer</type>)
+     <indexterm>
+      <primary><varname>auto_explain.log_parameter_max_length</varname> configuration parameter</primary>
+     </indexterm>
+    </term>
+    <listitem>
+     <para>
+      <varname>auto_explain.log_parameter_max_length</varname> controls the
+      logging of query parameter values.  A value of<literal>-1</literal> (the
+      default) logs the parameter values in full. <literal>0</literal> disables
+      logging of parameter values.  A value greater than zero truncates each
+      parameter value to that many bytes. Only superusers can change this
+      setting.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term>
      <varname>auto_explain.log_analyze</varname> (<type>boolean</type>)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 5d1f7089da..8584e3585d 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -972,6 +972,28 @@ ExplainQueryText(ExplainState *es, QueryDesc *queryDesc)
         ExplainPropertyText("Query Text", queryDesc->sourceText, es);
 }
 
+/*
+ * ExplainQueryParameters -
+ *      add a "Query Parameters" node that describes the parameters of the query,
+ *      if any
+ *
+ * The caller should have set up the options fields of *es, as well as
+ * initializing the output buffer es->str.
+ *
+ */
+void
+ExplainQueryParameters(ExplainState *es, ParamListInfo params, int maxlen)
+{
+    char       *str;
+
+    if (params == NULL || params->numParams == 0 || maxlen == 0)
+        return;
+
+    str = BuildParamLogString(params, NULL, maxlen);
+    if (str && str[0] != '\0')
+        ExplainPropertyText("Query Parameters", str, es);
+}
+
 /*
  * report_triggers -
  *        report execution stats for a single relation's triggers
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 666977fb1f..9ebde089ae 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -99,6 +99,7 @@ extern void ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc);
 extern void ExplainPrintJITSummary(ExplainState *es, QueryDesc *queryDesc);
 
 extern void ExplainQueryText(ExplainState *es, QueryDesc *queryDesc);
+extern void ExplainQueryParameters(ExplainState *es, ParamListInfo params, int maxlen);
 
 extern void ExplainBeginOutput(ExplainState *es);
 extern void ExplainEndOutput(ExplainState *es);
-- 
2.30.2


В списке pgsql-hackers по дате отправления:

Предыдущее
От: John Naylor
Дата:
Сообщение: Re: [PoC] Improve dead tuple storage for lazy vacuum
Следующее
От: Dagfinn Ilmari Mannsåker
Дата:
Сообщение: Re: Logging query parmeters in auto_explain