]> granicus.if.org Git - postgresql/commitdiff
Limit the verbosity of memory context statistics dumps.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 25 Aug 2015 17:09:48 +0000 (13:09 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 25 Aug 2015 17:09:48 +0000 (13:09 -0400)
We had a report from Stefan Kaltenbrunner of a case in which postmaster
log files overran available disk space because multiple backends spewed
enormous context stats dumps upon hitting an out-of-memory condition.
Given the lack of similar reports, this isn't a common problem, but it
still seems worth doing something about.  However, we don't want to just
blindly truncate the output, because that might prevent diagnosis of OOM
problems.  What seems like a workable compromise is to limit the dump to
100 child contexts per parent, and summarize the space used within any
additional child contexts.  That should help because 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, we hope.
Anyway it doesn't take much code or memory space to do this, so let's try
it like this and see how things go.

Since the summarization mechanism requires passing totals back up anyway,
I took the opportunity to add a "grand total" line to the end of the
printout.

src/backend/utils/mmgr/aset.c
src/backend/utils/mmgr/mcxt.c
src/include/nodes/memnodes.h
src/include/utils/memutils.h

index febeb6eaf8eb966f7c50e4368b4af5c67ec205d2..70759abd31f1f2a4b51be562e58c05edfa5df6b8 100644 (file)
@@ -253,7 +253,8 @@ static void AllocSetReset(MemoryContext context);
 static void AllocSetDelete(MemoryContext context);
 static Size AllocSetGetChunkSpace(MemoryContext context, void *pointer);
 static bool AllocSetIsEmpty(MemoryContext context);
-static void AllocSetStats(MemoryContext context, int level);
+static void AllocSetStats(MemoryContext context, int level, bool print,
+                         MemoryContextCounters *totals);
 
 #ifdef MEMORY_CONTEXT_CHECKING
 static void AllocSetCheck(MemoryContext context);
@@ -1228,20 +1229,23 @@ AllocSetIsEmpty(MemoryContext context)
 
 /*
  * AllocSetStats
- *             Displays stats about memory consumption of an allocset.
+ *             Compute stats about memory consumption of an allocset.
+ *
+ * level: recursion level (0 at top level); used for print indentation.
+ * print: true to print stats to stderr.
+ * totals: if not NULL, add stats about this allocset into *totals.
  */
 static void
-AllocSetStats(MemoryContext context, int level)
+AllocSetStats(MemoryContext context, int level, bool print,
+                         MemoryContextCounters *totals)
 {
        AllocSet        set = (AllocSet) context;
        Size            nblocks = 0;
-       Size            nchunks = 0;
+       Size            freechunks = 0;
        Size            totalspace = 0;
        Size            freespace = 0;
        AllocBlock      block;
-       AllocChunk      chunk;
        int                     fidx;
-       int                     i;
 
        for (block = set->blocks; block != NULL; block = block->next)
        {
@@ -1251,21 +1255,35 @@ AllocSetStats(MemoryContext context, int level)
        }
        for (fidx = 0; fidx < ALLOCSET_NUM_FREELISTS; fidx++)
        {
+               AllocChunk      chunk;
+
                for (chunk = set->freelist[fidx]; chunk != NULL;
                         chunk = (AllocChunk) chunk->aset)
                {
-                       nchunks++;
+                       freechunks++;
                        freespace += chunk->size + ALLOC_CHUNKHDRSZ;
                }
        }
 
-       for (i = 0; i < level; i++)
-               fprintf(stderr, "  ");
+       if (print)
+       {
+               int                     i;
 
-       fprintf(stderr,
+               for (i = 0; i < level; i++)
+                       fprintf(stderr, "  ");
+               fprintf(stderr,
                        "%s: %zu total in %zd blocks; %zu free (%zd chunks); %zu used\n",
-                       set->header.name, totalspace, nblocks, freespace, nchunks,
-                       totalspace - freespace);
+                               set->header.name, totalspace, nblocks, freespace, freechunks,
+                               totalspace - freespace);
+       }
+
+       if (totals)
+       {
+               totals->nblocks += nblocks;
+               totals->freechunks += freechunks;
+               totals->totalspace += totalspace;
+               totals->freespace += freespace;
+       }
 }
 
 
index 12d29f7440a7c217e7abd27ee39176c66885b597..705f3ef279130cc49ef0ccd152a6ddd3cd9ad4a9 100644 (file)
@@ -52,7 +52,9 @@ MemoryContext CurTransactionContext = NULL;
 MemoryContext PortalContext = NULL;
 
 static void MemoryContextCallResetCallbacks(MemoryContext context);
-static void MemoryContextStatsInternal(MemoryContext context, int level);
+static void MemoryContextStatsInternal(MemoryContext context, int level,
+                                                  bool print, int max_children,
+                                                  MemoryContextCounters *totals);
 
 /*
  * You should not do memory allocations within a critical section, because
@@ -477,25 +479,106 @@ MemoryContextIsEmpty(MemoryContext context)
  * MemoryContextStats
  *             Print statistics about the named context and all its descendants.
  *
- * This is just a debugging utility, so it's not fancy.  The statistics
- * are merely sent to stderr.
+ * This is just a debugging utility, so it's not very fancy.  However, we do
+ * make some effort to summarize when the output would otherwise be very long.
+ * The statistics are sent to stderr.
  */
 void
 MemoryContextStats(MemoryContext context)
 {
-       MemoryContextStatsInternal(context, 0);
+       /* A hard-wired limit on the number of children is usually good enough */
+       MemoryContextStatsDetail(context, 100);
 }
 
+/*
+ * MemoryContextStatsDetail
+ *
+ * Entry point for use if you want to vary the number of child contexts shown.
+ */
+void
+MemoryContextStatsDetail(MemoryContext context, int max_children)
+{
+       MemoryContextCounters grand_totals;
+
+       memset(&grand_totals, 0, sizeof(grand_totals));
+
+       MemoryContextStatsInternal(context, 0, true, max_children, &grand_totals);
+
+       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);
+}
+
+/*
+ * MemoryContextStatsInternal
+ *             One recursion level for MemoryContextStats
+ *
+ * Print this context if print is true, but in any case accumulate counts into
+ * *totals (if given).
+ */
 static void
-MemoryContextStatsInternal(MemoryContext context, int level)
+MemoryContextStatsInternal(MemoryContext context, int level,
+                                                  bool print, int max_children,
+                                                  MemoryContextCounters *totals)
 {
+       MemoryContextCounters local_totals;
        MemoryContext child;
+       int                     ichild;
 
        AssertArg(MemoryContextIsValid(context));
 
-       (*context->methods->stats) (context, level);
-       for (child = context->firstchild; child != NULL; child = child->nextchild)
-               MemoryContextStatsInternal(child, level + 1);
+       /* Examine the context itself */
+       (*context->methods->stats) (context, level, print, totals);
+
+       /*
+        * Examine children.  If there are more than max_children of them, we do
+        * not print the rest explicitly, but just summarize them.
+        */
+       memset(&local_totals, 0, sizeof(local_totals));
+
+       for (child = context->firstchild, ichild = 0;
+                child != NULL;
+                child = child->nextchild, ichild++)
+       {
+               if (ichild < max_children)
+                       MemoryContextStatsInternal(child, level + 1,
+                                                                          print, max_children,
+                                                                          totals);
+               else
+                       MemoryContextStatsInternal(child, level + 1,
+                                                                          false, max_children,
+                                                                          &local_totals);
+       }
+
+       /* Deal with excess children */
+       if (ichild > max_children)
+       {
+               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 (totals)
+               {
+                       totals->nblocks += local_totals.nblocks;
+                       totals->freechunks += local_totals.freechunks;
+                       totals->totalspace += local_totals.totalspace;
+                       totals->freespace += local_totals.freespace;
+               }
+       }
 }
 
 /*
index 5e036b9b6f5dc892b8947caef2c91cc32bcc4874..577ab9cb1f9560ebc0364d7f390a52588fa16fea 100644 (file)
 
 #include "nodes/nodes.h"
 
+/*
+ * MemoryContextCounters
+ *             Summarization state for MemoryContextStats collection.
+ *
+ * The set of counters in this struct is biased towards AllocSet; if we ever
+ * add any context types that are based on fundamentally different approaches,
+ * we might need more or different counters here.  A possible API spec then
+ * would be to print only nonzero counters, but for now we just summarize in
+ * the format historically used by AllocSet.
+ */
+typedef struct MemoryContextCounters
+{
+       Size            nblocks;                /* Total number of malloc blocks */
+       Size            freechunks;             /* Total number of free chunks */
+       Size            totalspace;             /* Total bytes requested from malloc */
+       Size            freespace;              /* The unused portion of totalspace */
+} MemoryContextCounters;
+
 /*
  * MemoryContext
  *             A logical context in which memory allocations occur.
@@ -44,7 +62,8 @@ typedef struct MemoryContextMethods
        void            (*delete_context) (MemoryContext context);
        Size            (*get_chunk_space) (MemoryContext context, void *pointer);
        bool            (*is_empty) (MemoryContext context);
-       void            (*stats) (MemoryContext context, int level);
+       void            (*stats) (MemoryContext context, int level, bool print,
+                                                                         MemoryContextCounters *totals);
 #ifdef MEMORY_CONTEXT_CHECKING
        void            (*check) (MemoryContext context);
 #endif
index f0fe0f449cac4b8e96ac4b6d0927ec593b6830bb..38106d79e21da6ad79fd6090da638f23afddb724 100644 (file)
@@ -104,6 +104,7 @@ extern MemoryContext GetMemoryChunkContext(void *pointer);
 extern MemoryContext MemoryContextGetParent(MemoryContext context);
 extern bool MemoryContextIsEmpty(MemoryContext context);
 extern void MemoryContextStats(MemoryContext context);
+extern void MemoryContextStatsDetail(MemoryContext context, int max_children);
 extern void MemoryContextAllowInCriticalSection(MemoryContext context,
                                                                        bool allow);