Add function to log the memory contexts of specified backend process.
authorFujii Masao <fujii@postgresql.org>
Tue, 6 Apr 2021 04:44:15 +0000 (13:44 +0900)
committerFujii Masao <fujii@postgresql.org>
Tue, 6 Apr 2021 04:44:15 +0000 (13:44 +0900)
Commit 3e98c0bafb added pg_backend_memory_contexts view to display
the memory contexts of the backend process. However its target process
is limited to the backend that is accessing to the view. So this is
not so convenient when investigating the local memory bloat of other
backend process. To improve this situation, this commit adds
pg_log_backend_memory_contexts() function that requests to log
the memory contexts of the specified backend process.

This information can be also collected by calling
MemoryContextStats(TopMemoryContext) via a debugger. But
this technique cannot be used in some environments because no debugger
is available there. So, pg_log_backend_memory_contexts() allows us to
see the memory contexts of specified backend more easily.

Only superusers are allowed to request to log the memory contexts
because allowing any users to issue this request at an unbounded rate
would cause lots of log messages and which can lead to denial of service.

On receipt of the request, at the next CHECK_FOR_INTERRUPTS(),
the target backend logs its memory contexts at LOG_SERVER_ONLY level,
so that these memory contexts will appear in the server log but not
be sent to the client. It logs one message per memory context.
Because if it buffers all memory contexts into StringInfo to log them
as one message, which may require the buffer to be enlarged very much
and lead to OOM error since there can be a large number of memory
contexts in a backend.

When a backend process is consuming huge memory, logging all its
memory contexts might overrun available disk space. To prevent this,
now this patch limits the number of child contexts to log per parent
to 100. As with MemoryContextStats(), it supposes that practical cases
where the log gets long will typically be huge numbers of siblings
under the same parent context; while the additional debugging value
from seeing details about individual siblings beyond 100 will not be large.

There was another proposed patch to add the function to return
the memory contexts of specified backend as the result sets,
instead of logging them, in the discussion. However that patch is
not included in this commit because it had several issues to address.

Thanks to Tatsuhito Kasahara, Andres Freund, Tom Lane, Tomas Vondra,
Michael Paquier, Kyotaro Horiguchi and Zhihong Yu for the discussion.

Bump catalog version.

Author: Atsushi Torikoshi
Reviewed-by: Kyotaro Horiguchi, Zhihong Yu, Fujii Masao
Discussion: https://postgr.es/m/0271f440ac77f2a4180e0e56ebd944d1@oss.nttdata.com

17 files changed:
doc/src/sgml/func.sgml
src/backend/storage/ipc/procsignal.c
src/backend/tcop/postgres.c
src/backend/utils/adt/mcxtfuncs.c
src/backend/utils/init/globals.c
src/backend/utils/mmgr/aset.c
src/backend/utils/mmgr/generation.c
src/backend/utils/mmgr/mcxt.c
src/backend/utils/mmgr/slab.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/include/miscadmin.h
src/include/nodes/memnodes.h
src/include/storage/procsignal.h
src/include/utils/memutils.h
src/test/regress/expected/misc_functions.out
src/test/regress/sql/misc_functions.sql

index 3cf243a16adab1a85885cb97264f4e71ca6f9e14..9fcee74910514aec579dda97140f470f13fd3668 100644 (file)
@@ -24913,6 +24913,26 @@ SELECT collation for ('foo' COLLATE "de_DE");
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_log_backend_memory_contexts</primary>
+        </indexterm>
+        <function>pg_log_backend_memory_contexts</function> ( <parameter>pid</parameter> <type>integer</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Requests to log the memory contexts whose backend process has
+        the specified process ID.  These memory contexts will be logged at
+        <literal>LOG</literal> message level. They will appear in
+        the server log based on the log configuration set
+        (See <xref linkend="runtime-config-logging"/> for more information),
+        but will not be sent to the client whatever the setting of
+        <xref linkend="guc-client-min-messages"/>.
+        Only superusers can request to log the memory contexts.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
@@ -24983,6 +25003,38 @@ SELECT collation for ('foo' COLLATE "de_DE");
     <structname>pg_stat_activity</structname> view.
    </para>
 
+   <para>
+    <function>pg_log_backend_memory_contexts</function> can be used
+    to log the memory contexts of the backend process. For example,
+<programlisting>
+postgres=# SELECT pg_log_backend_memory_contexts(pg_backend_pid());
+ pg_log_backend_memory_contexts 
+--------------------------------
+ t
+(1 row)
+</programlisting>
+One message for each memory context will be logged. For example:
+<screen>
+LOG:  logging memory contexts of PID 10377
+STATEMENT:  SELECT pg_log_backend_memory_contexts(pg_backend_pid());
+LOG:  level: 0; TopMemoryContext: 80800 total in 6 blocks; 14432 free (5 chunks); 66368 used
+LOG:  level: 1; pgstat TabStatusArray lookup hash table: 8192 total in 1 blocks; 1408 free (0 chunks); 6784 used
+LOG:  level: 1; TopTransactionContext: 8192 total in 1 blocks; 7720 free (1 chunks); 472 used
+LOG:  level: 1; RowDescriptionContext: 8192 total in 1 blocks; 6880 free (0 chunks); 1312 used
+LOG:  level: 1; MessageContext: 16384 total in 2 blocks; 5152 free (0 chunks); 11232 used
+LOG:  level: 1; Operator class cache: 8192 total in 1 blocks; 512 free (0 chunks); 7680 used
+LOG:  level: 1; smgr relation table: 16384 total in 2 blocks; 4544 free (3 chunks); 11840 used
+LOG:  level: 1; TransactionAbortContext: 32768 total in 1 blocks; 32504 free (0 chunks); 264 used
+...
+LOG:  level: 1; ErrorContext: 8192 total in 1 blocks; 7928 free (3 chunks); 264 used
+LOG:  Grand total: 1651920 bytes in 201 blocks; 622360 free (88 chunks); 1029560 used
+</screen>
+    For more than 100 child contexts under the same parent one,
+    100 child contexts and a summary of the remaining ones will be logged.
+    Note that frequent calls to this function could incur significant overhead,
+    because it may generate a large number of log messages.
+   </para>
+
   </sect2>
 
   <sect2 id="functions-admin-backup">
index c6a8d4611e423a16466d0a6fe2c59654e18aabbc..eac6895141455fe2ac5e32fcafec999f598111aa 100644 (file)
@@ -30,6 +30,7 @@
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "tcop/tcopprot.h"
+#include "utils/memutils.h"
 
 /*
  * The SIGUSR1 signal is multiplexed to support signaling multiple event
@@ -657,6 +658,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
    if (CheckProcSignal(PROCSIG_BARRIER))
        HandleProcSignalBarrierInterrupt();
 
+   if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
+       HandleLogMemoryContextInterrupt();
+
    if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_DATABASE))
        RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_DATABASE);
 
index ad351e2fd1e613095fc8441c39232463dff40eee..330ec5b02889035b1d3120693ceecc5f662c9164 100644 (file)
@@ -3327,6 +3327,9 @@ ProcessInterrupts(void)
 
    if (ParallelMessagePending)
        HandleParallelMessages();
+
+   if (LogMemoryContextPending)
+       ProcessLogMemoryContextInterrupt();
 }
 
 
index c02fa47550a6d8a8519a7f4d5fcf534d20df1517..e2b87a7ed9f678815aba5bbc8f796ac05117ad7c 100644 (file)
@@ -18,6 +18,8 @@
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "mb/pg_wchar.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
 #include "utils/builtins.h"
 
 /* ----------
@@ -61,7 +63,7 @@ PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
 
    /* Examine the context itself */
    memset(&stat, 0, sizeof(stat));
-   (*context->methods->stats) (context, NULL, (void *) &level, &stat);
+   (*context->methods->stats) (context, NULL, (void *) &level, &stat, true);
 
    memset(values, 0, sizeof(values));
    memset(nulls, 0, sizeof(nulls));
@@ -155,3 +157,59 @@ pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
 
    return (Datum) 0;
 }
+
+/*
+ * pg_log_backend_memory_contexts
+ *     Signal a backend process to log its memory contexts.
+ *
+ * Only superusers are allowed to signal to log the memory contexts
+ * because allowing any users to issue this request at an unbounded
+ * rate would cause lots of log messages and which can lead to
+ * denial of service.
+ *
+ * On receipt of this signal, a backend sets the flag in the signal
+ * handler, which causes the next CHECK_FOR_INTERRUPTS() to log the
+ * memory contexts.
+ */
+Datum
+pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
+{
+   int         pid = PG_GETARG_INT32(0);
+   PGPROC     *proc = BackendPidGetProc(pid);
+
+   /*
+    * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+    * we reach kill(), a process for which we get a valid proc here might
+    * have terminated on its own.  There's no way to acquire a lock on an
+    * arbitrary process to prevent that. But since this mechanism is usually
+    * used to debug a backend running and consuming lots of memory, that it
+    * might end on its own first and its memory contexts are not logged is
+    * not a problem.
+    */
+   if (proc == NULL)
+   {
+       /*
+        * This is just a warning so a loop-through-resultset will not abort
+        * if one backend terminated on its own during the run.
+        */
+       ereport(WARNING,
+               (errmsg("PID %d is not a PostgreSQL server process", pid)));
+       PG_RETURN_BOOL(false);
+   }
+
+   /* Only allow superusers to log memory contexts. */
+   if (!superuser())
+       ereport(ERROR,
+               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                errmsg("must be a superuser to log memory contexts")));
+
+   if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, proc->backendId) < 0)
+   {
+       /* Again, just a warning to allow loops */
+       ereport(WARNING,
+               (errmsg("could not send signal to process %d: %m", pid)));
+       PG_RETURN_BOOL(false);
+   }
+
+   PG_RETURN_BOOL(true);
+}
index a9f0fc3017c9433a23778140b203d052230f4a02..381d9e548d13df0654c270015948e42680e13cdd 100644 (file)
@@ -35,6 +35,7 @@ volatile sig_atomic_t ClientConnectionLost = false;
 volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
 volatile sig_atomic_t IdleSessionTimeoutPending = false;
 volatile sig_atomic_t ProcSignalBarrierPending = false;
+volatile sig_atomic_t LogMemoryContextPending = false;
 volatile uint32 InterruptHoldoffCount = 0;
 volatile uint32 QueryCancelHoldoffCount = 0;
 volatile uint32 CritSectionCount = 0;
index ec6c130d0fb2c47776f476fbf7676293b16a6c1e..77872e77bcd45d5ebe0033677c8b758b07442332 100644 (file)
@@ -272,7 +272,8 @@ static Size AllocSetGetChunkSpace(MemoryContext context, void *pointer);
 static bool AllocSetIsEmpty(MemoryContext context);
 static void AllocSetStats(MemoryContext context,
                          MemoryStatsPrintFunc printfunc, void *passthru,
-                         MemoryContextCounters *totals);
+                         MemoryContextCounters *totals,
+                         bool print_to_stderr);
 
 #ifdef MEMORY_CONTEXT_CHECKING
 static void AllocSetCheck(MemoryContext context);
@@ -1336,11 +1337,12 @@ AllocSetIsEmpty(MemoryContext context)
  * printfunc: if not NULL, pass a human-readable stats string to this.
  * passthru: pass this pointer through to printfunc.
  * totals: if not NULL, add stats about this context into *totals.
+ * print_to_stderr: print stats to stderr if true, elog otherwise.
  */
 static void
 AllocSetStats(MemoryContext context,
              MemoryStatsPrintFunc printfunc, void *passthru,
-             MemoryContextCounters *totals)
+             MemoryContextCounters *totals, bool print_to_stderr)
 {
    AllocSet    set = (AllocSet) context;
    Size        nblocks = 0;
@@ -1379,7 +1381,7 @@ AllocSetStats(MemoryContext context,
                 "%zu total in %zd blocks; %zu free (%zd chunks); %zu used",
                 totalspace, nblocks, freespace, freechunks,
                 totalspace - freespace);
-       printfunc(context, passthru, stats_string);
+       printfunc(context, passthru, stats_string, print_to_stderr);
    }
 
    if (totals)
index 2b90034764580b5d740e9c39e2b852db29e5310d..584cd614da06e1af286c68c84f1313d3d6866e88 100644 (file)
@@ -155,7 +155,8 @@ static Size GenerationGetChunkSpace(MemoryContext context, void *pointer);
 static bool GenerationIsEmpty(MemoryContext context);
 static void GenerationStats(MemoryContext context,
                            MemoryStatsPrintFunc printfunc, void *passthru,
-                           MemoryContextCounters *totals);
+                           MemoryContextCounters *totals,
+                           bool print_to_stderr);
 
 #ifdef MEMORY_CONTEXT_CHECKING
 static void GenerationCheck(MemoryContext context);
@@ -665,6 +666,7 @@ GenerationIsEmpty(MemoryContext context)
  * printfunc: if not NULL, pass a human-readable stats string to this.
  * passthru: pass this pointer through to printfunc.
  * totals: if not NULL, add stats about this context into *totals.
+ * print_to_stderr: print stats to stderr if true, elog otherwise.
  *
  * XXX freespace only accounts for empty space at the end of the block, not
  * space of freed chunks (which is unknown).
@@ -672,7 +674,7 @@ GenerationIsEmpty(MemoryContext context)
 static void
 GenerationStats(MemoryContext context,
                MemoryStatsPrintFunc printfunc, void *passthru,
-               MemoryContextCounters *totals)
+               MemoryContextCounters *totals, bool print_to_stderr)
 {
    GenerationContext *set = (GenerationContext *) context;
    Size        nblocks = 0;
@@ -704,7 +706,7 @@ GenerationStats(MemoryContext context,
                 "%zu total in %zd blocks (%zd chunks); %zu free (%zd chunks); %zu used",
                 totalspace, nblocks, nchunks, freespace,
                 nfreechunks, totalspace - freespace);
-       printfunc(context, passthru, stats_string);
+       printfunc(context, passthru, stats_string, print_to_stderr);
    }
 
    if (totals)
index 84472b9158e7be5521475facc05c07c9a34d6a15..6919a732804639fb93044e82ea212f9d0c5176ee 100644 (file)
 
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/procsignal.h"
+#include "utils/fmgrprotos.h"
 #include "utils/memdebug.h"
 #include "utils/memutils.h"
 
@@ -55,9 +59,11 @@ MemoryContext PortalContext = NULL;
 static void MemoryContextCallResetCallbacks(MemoryContext context);
 static void MemoryContextStatsInternal(MemoryContext context, int level,
                                       bool print, int max_children,
-                                      MemoryContextCounters *totals);
+                                      MemoryContextCounters *totals,
+                                      bool print_to_stderr);
 static void MemoryContextStatsPrint(MemoryContext context, void *passthru,
-                                   const char *stats_string);
+                                   const char *stats_string,
+                                   bool print_to_stderr);
 
 /*
  * You should not do memory allocations within a critical section, because
@@ -499,28 +505,52 @@ void
 MemoryContextStats(MemoryContext context)
 {
    /* A hard-wired limit on the number of children is usually good enough */
-   MemoryContextStatsDetail(context, 100);
+   MemoryContextStatsDetail(context, 100, true);
 }
 
 /*
  * MemoryContextStatsDetail
  *
  * Entry point for use if you want to vary the number of child contexts shown.
+ *
+ * If print_to_stderr is true, print statistics about the memory contexts
+ * with fprintf(stderr), otherwise use ereport().
  */
 void
-MemoryContextStatsDetail(MemoryContext context, int max_children)
+MemoryContextStatsDetail(MemoryContext context, int max_children,
+                        bool print_to_stderr)
 {
    MemoryContextCounters grand_totals;
 
    memset(&grand_totals, 0, sizeof(grand_totals));
 
-   MemoryContextStatsInternal(context, 0, true, max_children, &grand_totals);
+   MemoryContextStatsInternal(context, 0, true, max_children, &grand_totals, print_to_stderr);
+
+   if (print_to_stderr)
+       fprintf(stderr,
+               "Grand total: %zu bytes in %zd blocks; %zu free (%zd chunks); %zu used\n",
+               grand_totals.totalspace, grand_totals.nblocks,
+               grand_totals.freespace, grand_totals.freechunks,
+               grand_totals.totalspace - grand_totals.freespace);
+   else
 
-   fprintf(stderr,
-           "Grand total: %zu bytes in %zd blocks; %zu free (%zd chunks); %zu used\n",
-           grand_totals.totalspace, grand_totals.nblocks,
-           grand_totals.freespace, grand_totals.freechunks,
-           grand_totals.totalspace - grand_totals.freespace);
+       /*
+        * Use LOG_SERVER_ONLY to prevent the memory contexts from being sent
+        * to the connected client.
+        *
+        * We don't buffer the information about all memory contexts in a
+        * backend into StringInfo and log it as one message. Otherwise which
+        * may require the buffer to be enlarged very much and lead to OOM
+        * error since there can be a large number of memory contexts in a
+        * backend. Instead, we log one message per memory context.
+        */
+       ereport(LOG_SERVER_ONLY,
+               (errhidestmt(true),
+                errhidecontext(true),
+                errmsg_internal("Grand total: %zu bytes in %zd blocks; %zu free (%zd chunks); %zu used",
+                                grand_totals.totalspace, grand_totals.nblocks,
+                                grand_totals.freespace, grand_totals.freechunks,
+                                grand_totals.totalspace - grand_totals.freespace)));
 }
 
 /*
@@ -533,7 +563,8 @@ MemoryContextStatsDetail(MemoryContext context, int max_children)
 static void
 MemoryContextStatsInternal(MemoryContext context, int level,
                           bool print, int max_children,
-                          MemoryContextCounters *totals)
+                          MemoryContextCounters *totals,
+                          bool print_to_stderr)
 {
    MemoryContextCounters local_totals;
    MemoryContext child;
@@ -545,7 +576,7 @@ MemoryContextStatsInternal(MemoryContext context, int level,
    context->methods->stats(context,
                            print ? MemoryContextStatsPrint : NULL,
                            (void *) &level,
-                           totals);
+                           totals, print_to_stderr);
 
    /*
     * Examine children.  If there are more than max_children of them, we do
@@ -560,11 +591,13 @@ MemoryContextStatsInternal(MemoryContext context, int level,
        if (ichild < max_children)
            MemoryContextStatsInternal(child, level + 1,
                                       print, max_children,
-                                      totals);
+                                      totals,
+                                      print_to_stderr);
        else
            MemoryContextStatsInternal(child, level + 1,
                                       false, max_children,
-                                      &local_totals);
+                                      &local_totals,
+                                      print_to_stderr);
    }
 
    /* Deal with excess children */
@@ -572,18 +605,33 @@ MemoryContextStatsInternal(MemoryContext context, int level,
    {
        if (print)
        {
-           int         i;
-
-           for (i = 0; i <= level; i++)
-               fprintf(stderr, "  ");
-           fprintf(stderr,
-                   "%d more child contexts containing %zu total in %zd blocks; %zu free (%zd chunks); %zu used\n",
-                   ichild - max_children,
-                   local_totals.totalspace,
-                   local_totals.nblocks,
-                   local_totals.freespace,
-                   local_totals.freechunks,
-                   local_totals.totalspace - local_totals.freespace);
+           if (print_to_stderr)
+           {
+               int         i;
+
+               for (i = 0; i <= level; i++)
+                   fprintf(stderr, "  ");
+               fprintf(stderr,
+                       "%d more child contexts containing %zu total in %zd blocks; %zu free (%zd chunks); %zu used\n",
+                       ichild - max_children,
+                       local_totals.totalspace,
+                       local_totals.nblocks,
+                       local_totals.freespace,
+                       local_totals.freechunks,
+                       local_totals.totalspace - local_totals.freespace);
+           }
+           else
+               ereport(LOG_SERVER_ONLY,
+                       (errhidestmt(true),
+                        errhidecontext(true),
+                        errmsg_internal("level: %d; %d more child contexts containing %zu total in %zd blocks; %zu free (%zd chunks); %zu used",
+                                        level,
+                                        ichild - max_children,
+                                        local_totals.totalspace,
+                                        local_totals.nblocks,
+                                        local_totals.freespace,
+                                        local_totals.freechunks,
+                                        local_totals.totalspace - local_totals.freespace)));
        }
 
        if (totals)
@@ -605,11 +653,13 @@ MemoryContextStatsInternal(MemoryContext context, int level,
  */
 static void
 MemoryContextStatsPrint(MemoryContext context, void *passthru,
-                       const char *stats_string)
+                       const char *stats_string,
+                       bool print_to_stderr)
 {
    int         level = *(int *) passthru;
    const char *name = context->name;
    const char *ident = context->ident;
+   char        truncated_ident[110];
    int         i;
 
    /*
@@ -623,9 +673,8 @@ MemoryContextStatsPrint(MemoryContext context, void *passthru,
        ident = NULL;
    }
 
-   for (i = 0; i < level; i++)
-       fprintf(stderr, "  ");
-   fprintf(stderr, "%s: %s", name, stats_string);
+   truncated_ident[0] = '\0';
+
    if (ident)
    {
        /*
@@ -637,24 +686,41 @@ MemoryContextStatsPrint(MemoryContext context, void *passthru,
        int         idlen = strlen(ident);
        bool        truncated = false;
 
+       strcpy(truncated_ident, ": ");
+       i = strlen(truncated_ident);
+
        if (idlen > 100)
        {
            idlen = pg_mbcliplen(ident, idlen, 100);
            truncated = true;
        }
-       fprintf(stderr, ": ");
+
        while (idlen-- > 0)
        {
            unsigned char c = *ident++;
 
            if (c < ' ')
                c = ' ';
-           fputc(c, stderr);
+           truncated_ident[i++] = c;
        }
+       truncated_ident[i] = '\0';
+
        if (truncated)
-           fprintf(stderr, "...");
+           strcat(truncated_ident, "...");
+   }
+
+   if (print_to_stderr)
+   {
+       for (i = 0; i < level; i++)
+           fprintf(stderr, "  ");
+       fprintf(stderr, "%s: %s%s\n", name, stats_string, truncated_ident);
    }
-   fputc('\n', stderr);
+   else
+       ereport(LOG_SERVER_ONLY,
+               (errhidestmt(true),
+                errhidecontext(true),
+                errmsg_internal("level: %d; %s: %s%s",
+                                level, name, stats_string, truncated_ident)));
 }
 
 /*
@@ -946,6 +1012,52 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
    return ret;
 }
 
+/*
+ * HandleLogMemoryContextInterrupt
+ *     Handle receipt of an interrupt indicating logging of memory
+ *     contexts.
+ *
+ * All the actual work is deferred to ProcessLogMemoryContextInterrupt(),
+ * because we cannot safely emit a log message inside the signal handler.
+ */
+void
+HandleLogMemoryContextInterrupt(void)
+{
+   InterruptPending = true;
+   LogMemoryContextPending = true;
+   /* latch will be set by procsignal_sigusr1_handler */
+}
+
+/*
+ * ProcessLogMemoryContextInterrupt
+ *         Perform logging of memory contexts of this backend process.
+ *
+ * Any backend that participates in ProcSignal signaling must arrange
+ * to call this function if we see LogMemoryContextPending set.
+ * It is called from CHECK_FOR_INTERRUPTS(), which is enough because
+ * the target process for logging of memory contexts is a backend.
+ */
+void
+ProcessLogMemoryContextInterrupt(void)
+{
+   LogMemoryContextPending = false;
+
+   ereport(LOG,
+           (errmsg("logging memory contexts of PID %d", MyProcPid)));
+
+   /*
+    * When a backend process is consuming huge memory, logging all its memory
+    * contexts might overrun available disk space. To prevent this, we limit
+    * the number of child contexts to log per parent to 100.
+    *
+    * As with MemoryContextStats(), we suppose that practical cases where the
+    * dump gets long will typically be huge numbers of siblings under the
+    * same parent context; while the additional debugging value from seeing
+    * details about individual siblings beyond 100 will not be large.
+    */
+   MemoryContextStatsDetail(TopMemoryContext, 100, false);
+}
+
 void *
 palloc(Size size)
 {
index 9213be7c9569fc1be810b8835badd73f171bcef7..553dd7f66746c7af94000c95c8f9b29f8ff683ac 100644 (file)
@@ -135,7 +135,8 @@ static Size SlabGetChunkSpace(MemoryContext context, void *pointer);
 static bool SlabIsEmpty(MemoryContext context);
 static void SlabStats(MemoryContext context,
                      MemoryStatsPrintFunc printfunc, void *passthru,
-                     MemoryContextCounters *totals);
+                     MemoryContextCounters *totals,
+                     bool print_to_stderr);
 #ifdef MEMORY_CONTEXT_CHECKING
 static void SlabCheck(MemoryContext context);
 #endif
@@ -632,11 +633,13 @@ SlabIsEmpty(MemoryContext context)
  * printfunc: if not NULL, pass a human-readable stats string to this.
  * passthru: pass this pointer through to printfunc.
  * totals: if not NULL, add stats about this context into *totals.
+ * print_to_stderr: print stats to stderr if true, elog otherwise.
  */
 static void
 SlabStats(MemoryContext context,
          MemoryStatsPrintFunc printfunc, void *passthru,
-         MemoryContextCounters *totals)
+         MemoryContextCounters *totals,
+         bool print_to_stderr)
 {
    SlabContext *slab = castNode(SlabContext, context);
    Size        nblocks = 0;
@@ -671,7 +674,7 @@ SlabStats(MemoryContext context,
                 "%zu total in %zd blocks; %zu free (%zd chunks); %zu used",
                 totalspace, nblocks, freespace, freechunks,
                 totalspace - freespace);
-       printfunc(context, passthru, stats_string);
+       printfunc(context, passthru, stats_string, print_to_stderr);
    }
 
    if (totals)
index 7eaca64b559cbab513850a12d9cddf7248ab94d6..613823c6ee8ca06cfb2966a82633217bf9a15abf 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202104051
+#define CATALOG_VERSION_NO 202104061
 
 #endif
index 69ffd0c3f4dc313670f315dbc02269f94f855fb8..73c22c8b4df3b7a7739c96f9ec7b212ca7253c6a 100644 (file)
   proargnames => '{name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes}',
   prosrc => 'pg_get_backend_memory_contexts' },
 
+# logging memory contexts of the specified backend
+{ oid => '4543', descr => 'log memory contexts of the specified backend',
+  proname => 'pg_log_backend_memory_contexts',
+  provolatile => 'v', prorettype => 'bool',
+  proargtypes => 'int4', prosrc => 'pg_log_backend_memory_contexts' },
+
 # non-persistent series generator
 { oid => '1066', descr => 'non-persistent series generator',
   proname => 'generate_series', prorows => '1000',
index 6f8251e0b07d0ed2f39f237465a9ae201fb1d687..95202d37af5d3f41755b0a6b807af95556baf490 100644 (file)
@@ -84,6 +84,7 @@ extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
 extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
 extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending;
 extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
+extern PGDLLIMPORT volatile sig_atomic_t LogMemoryContextPending;
 
 extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending;
 extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
index 9331ef80fd9d1f6e64863346fefd6e3f129d8386..e6a757d6a07561c1c15634db6a5720c1bc19ca5f 100644 (file)
@@ -52,7 +52,8 @@ typedef struct MemoryContextCounters
  */
 
 typedef void (*MemoryStatsPrintFunc) (MemoryContext context, void *passthru,
-                                     const char *stats_string);
+                                     const char *stats_string,
+                                     bool print_to_stderr);
 
 typedef struct MemoryContextMethods
 {
@@ -66,7 +67,8 @@ typedef struct MemoryContextMethods
    bool        (*is_empty) (MemoryContext context);
    void        (*stats) (MemoryContext context,
                          MemoryStatsPrintFunc printfunc, void *passthru,
-                         MemoryContextCounters *totals);
+                         MemoryContextCounters *totals,
+                         bool print_to_stderr);
 #ifdef MEMORY_CONTEXT_CHECKING
    void        (*check) (MemoryContext context);
 #endif
index 4ae7dc33b8e12d06518bbbb185b2421854f8ae80..eec186be2ee707820bdf4df52e7efc72ac8de8ca 100644 (file)
@@ -34,6 +34,7 @@ typedef enum
    PROCSIG_PARALLEL_MESSAGE,   /* message from cooperating parallel backend */
    PROCSIG_WALSND_INIT_STOPPING,   /* ask walsenders to prepare for shutdown  */
    PROCSIG_BARRIER,            /* global barrier interrupt  */
+   PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
 
    /* Recovery conflict reasons */
    PROCSIG_RECOVERY_CONFLICT_DATABASE,
index 36aae4e51c8f067f5176b2c26dad115615bdb289..ff872274d44fbf96b5525bc87840464f5c0efed2 100644 (file)
@@ -84,7 +84,8 @@ extern MemoryContext MemoryContextGetParent(MemoryContext context);
 extern bool MemoryContextIsEmpty(MemoryContext context);
 extern Size MemoryContextMemAllocated(MemoryContext context, bool recurse);
 extern void MemoryContextStats(MemoryContext context);
-extern void MemoryContextStatsDetail(MemoryContext context, int max_children);
+extern void MemoryContextStatsDetail(MemoryContext context, int max_children,
+                                    bool print_to_stderr);
 extern void MemoryContextAllowInCriticalSection(MemoryContext context,
                                                bool allow);
 
@@ -144,6 +145,8 @@ extern void MemoryContextCreate(MemoryContext node,
                                MemoryContext parent,
                                const char *name);
 
+extern void HandleLogMemoryContextInterrupt(void);
+extern void ProcessLogMemoryContextInterrupt(void);
 
 /*
  * Memory-context-type-specific functions
index d3acb98d04e1f1a25ca436f38654d6881f5136a8..e845042d38de7c69fffc6153fe19760e2228a183 100644 (file)
@@ -133,6 +133,19 @@ ERROR:  function num_nulls() does not exist
 LINE 1: SELECT num_nulls();
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- pg_log_backend_memory_contexts()
+--
+-- Memory contexts are logged and they are not returned to the function.
+-- Furthermore, their contents can vary depending on the timing. However,
+-- we can at least verify that the code doesn't fail.
+--
+SELECT * FROM pg_log_backend_memory_contexts(pg_backend_pid());
+ pg_log_backend_memory_contexts 
+--------------------------------
+ t
+(1 row)
+
 --
 -- Test some built-in SRFs
 --
index 094e8f8296fbd8a1a3af304478a2b5edfcafc130..a398349afc63edb24e3afaa001d1d6d36eb0db01 100644 (file)
@@ -30,6 +30,15 @@ SELECT num_nulls(VARIADIC '{}'::int[]);
 SELECT num_nonnulls();
 SELECT num_nulls();
 
+--
+-- pg_log_backend_memory_contexts()
+--
+-- Memory contexts are logged and they are not returned to the function.
+-- Furthermore, their contents can vary depending on the timing. However,
+-- we can at least verify that the code doesn't fail.
+--
+SELECT * FROM pg_log_backend_memory_contexts(pg_backend_pid());
+
 --
 -- Test some built-in SRFs
 --