On Mon, 2020-04-06 at 23:39 +1200, David Rowley wrote:
> 1. The comment mentions "passthru", but you've removed that
> parameter.

Fixed, thank you.

> 2. I don't think MemoryContextCount is the best name for this
> function. When I saw:

I've gone back and forth on naming a bit. The right name, in my
opinion, is MemoryContextStats(), but that's taken by something that
should be called MemoryContextReport(). But I didn't want to change
that as it would probably annoy a lot of people who are used to calling
MemoryContextStats() from gdb.

I changed the new function to be called MemoryContextGetCounters(),
which is more directly what it's doing. "Telemetry" makes me think more
of a stream of information rather than a particular point in time.

> 3. I feel like it would be nicer if you didn't change the "count"
> methods to return a MemoryContextCounters. It means you may need to
> zero a struct for each level, assign the values, then add them to the
> total.  If you were just to zero the struct in MemoryContextCount()
> then pass it into the count function, then you could just have it do
> all the += work. It would reduce the code in MemoryContextCount()
> too.

I changed it to use a pointer out parameter, but I don't think an
in/out parameter is quite right there. MemoryContextStats() ends up
using both the per-context counters as well as the totals, so it's not
helpful to return just the totals.

> 4. Do you think it would be better to have two separate functions for
> MemoryContextCount(), a recursive version and a non-recursive
> version.

I could, but right now the only caller passes recurse=true, so I'm not
really eliminating any code in that path by specializing the functions.
Are you thinking about performance or you just think it would be better
to have two entry points?

> 5. For performance testing, I tried using the following table with
> 1MB
> work_mem then again with 1GB work_mem.  I wondered if making the
> accounting more complex would cause a slowdown in nodeAgg.c, as I see
> we're calling this function each time we get a tuple that belongs in
> a
> new group. The 1MB test is likely not such a great way to measure
> this
> since we do spill to disk in that case and the changing in accounting
> means we do that at a slightly different time, but the 1GB test does
> not spill and it's a bit slower.

I think this was because I was previously returning a struct. By just
passing a pointer as an out param, it seems to have mitigated it, but
not completely eliminated the cost (< 2% in my tests). I was using an
OFFSET 100000000 instead of EXPLAIN ANALYZE in my test measurements
because it was less noisy, and I focused on the 1GB test for the reason
you mention.

I also addressed Andres's comments:

* changed the name of the flags from MCXT_STAT to MCXT_COUNT
* changed ((1<<6)-1) to ~0
* removed unnecessary branches from the GetCounters method
* expanded some comments

Regards,
        Jeff Davis

diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 4e100e5755f..fd205071ca1 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -414,6 +414,7 @@ static void agg_fill_hash_table(AggState *aggstate);
 static bool agg_refill_hash_table(AggState *aggstate);
 static TupleTableSlot *agg_retrieve_hash_table(AggState *aggstate);
 static TupleTableSlot *agg_retrieve_hash_table_in_memory(AggState *aggstate);
+static Size hash_agg_check_memory(MemoryContext context);
 static void hash_agg_check_limits(AggState *aggstate);
 static void hash_agg_enter_spill_mode(AggState *aggstate);
 static void hash_agg_update_metrics(AggState *aggstate, bool from_tape,
@@ -1792,6 +1793,25 @@ hash_agg_set_limits(double hashentrysize, uint64 input_groups, int used_bits,
 		*ngroups_limit = 1;
 }
 
+/*
+ * Get the memory consumed by the memory context. Consider all memory except
+ * "newspace", which is the untouched part of a new block allocation by the
+ * memory context itself. The algorithm has little control over how eagerly
+ * the memory context allocates new memory, so it doesn't make sense to count
+ * it against work_mem.
+ */
+static Size
+hash_agg_check_memory(MemoryContext context)
+{
+	MemoryContextCounters	counters;
+	uint32					flags = (MCXT_COUNT_TOTALSPACE |
+									 MCXT_COUNT_NEWSPACE);
+
+	MemoryContextGetCounters(context, flags, true, &counters);
+
+	return counters.totalspace - counters.newspace;
+}
+
 /*
  * hash_agg_check_limits
  *
@@ -1802,11 +1822,17 @@ hash_agg_set_limits(double hashentrysize, uint64 input_groups, int used_bits,
 static void
 hash_agg_check_limits(AggState *aggstate)
 {
-	uint64 ngroups = aggstate->hash_ngroups_current;
-	Size meta_mem = MemoryContextMemAllocated(
-		aggstate->hash_metacxt, true);
-	Size hash_mem = MemoryContextMemAllocated(
-		aggstate->hashcontext->ecxt_per_tuple_memory, true);
+	uint64	ngroups = aggstate->hash_ngroups_current;
+	Size	meta_mem;
+	Size	hash_mem;
+
+	/* already in spill mode; nothing to do */
+	if (aggstate->hash_spill_mode)
+		return;
+
+	meta_mem = hash_agg_check_memory(aggstate->hash_metacxt);
+	hash_mem = hash_agg_check_memory(
+		aggstate->hashcontext->ecxt_per_tuple_memory);
 
 	/*
 	 * Don't spill unless there's at least one group in the hash table so we
@@ -1874,11 +1900,11 @@ hash_agg_update_metrics(AggState *aggstate, bool from_tape, int npartitions)
 		return;
 
 	/* memory for the hash table itself */
-	meta_mem = MemoryContextMemAllocated(aggstate->hash_metacxt, true);
+	meta_mem = hash_agg_check_memory(aggstate->hash_metacxt);
 
 	/* memory for the group keys and transition states */
-	hash_mem = MemoryContextMemAllocated(
-		aggstate->hashcontext->ecxt_per_tuple_memory, true);
+	hash_mem = hash_agg_check_memory(
+		aggstate->hashcontext->ecxt_per_tuple_memory);
 
 	/* memory for read/write tape buffers, if spilled */
 	buffer_mem = npartitions * HASHAGG_WRITE_BUFFER_SIZE;
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index c0623f106d2..1dfc6126ad4 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -132,6 +132,8 @@ typedef struct AllocSetContext
 	Size		maxBlockSize;	/* maximum block size */
 	Size		nextBlockSize;	/* next block size to allocate */
 	Size		allocChunkLimit;	/* effective chunk size limit */
+	Size		memAllocated;	/* track memory allocated for this context */
+	uint64		nChunks;		/* total number of chunks */
 	AllocBlock	keeper;			/* keep this block over resets */
 	/* freelist this context could be put in, or -1 if not a candidate: */
 	int			freeListIndex;	/* index in context_freelists[], or -1 */
@@ -273,9 +275,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,
-						  MemoryStatsPrintFunc printfunc, void *passthru,
-						  MemoryContextCounters *totals);
+static void AllocSetGetCounters(MemoryContext context, uint32 flags,
+								MemoryContextCounters *counters);
 
 #ifdef MEMORY_CONTEXT_CHECKING
 static void AllocSetCheck(MemoryContext context);
@@ -292,7 +293,7 @@ static const MemoryContextMethods AllocSetMethods = {
 	AllocSetDelete,
 	AllocSetGetChunkSpace,
 	AllocSetIsEmpty,
-	AllocSetStats
+	AllocSetGetCounters
 #ifdef MEMORY_CONTEXT_CHECKING
 	,AllocSetCheck
 #endif
@@ -464,8 +465,8 @@ AllocSetContextCreateInternal(MemoryContext parent,
 								parent,
 								name);
 
-			((MemoryContext) set)->mem_allocated =
-				set->keeper->endptr - ((char *) set);
+			set->memAllocated = set->keeper->endptr - ((char *) set);
+			set->nChunks = 0;
 
 			return (MemoryContext) set;
 		}
@@ -555,7 +556,8 @@ AllocSetContextCreateInternal(MemoryContext parent,
 						parent,
 						name);
 
-	((MemoryContext) set)->mem_allocated = firstBlockSize;
+	set->memAllocated = firstBlockSize;
+	set->nChunks = 0;
 
 	return (MemoryContext) set;
 }
@@ -617,7 +619,7 @@ AllocSetReset(MemoryContext context)
 		else
 		{
 			/* Normal case, release the block */
-			context->mem_allocated -= block->endptr - ((char*) block);
+			set->memAllocated -= block->endptr - ((char*) block);
 
 #ifdef CLOBBER_FREED_MEMORY
 			wipe_mem(block, block->freeptr - ((char *) block));
@@ -627,7 +629,9 @@ AllocSetReset(MemoryContext context)
 		block = next;
 	}
 
-	Assert(context->mem_allocated == keepersize);
+	Assert(set->memAllocated == keepersize);
+
+	set->nChunks = 0;
 
 	/* Reset block size allocation sequence, too */
 	set->nextBlockSize = set->initBlockSize;
@@ -703,7 +707,7 @@ AllocSetDelete(MemoryContext context)
 		AllocBlock	next = block->next;
 
 		if (block != set->keeper)
-			context->mem_allocated -= block->endptr - ((char *) block);
+			set->memAllocated -= block->endptr - ((char *) block);
 
 #ifdef CLOBBER_FREED_MEMORY
 		wipe_mem(block, block->freeptr - ((char *) block));
@@ -715,7 +719,7 @@ AllocSetDelete(MemoryContext context)
 		block = next;
 	}
 
-	Assert(context->mem_allocated == keepersize);
+	Assert(set->memAllocated == keepersize);
 
 	/* Finally, free the context header, including the keeper block */
 	free(set);
@@ -758,7 +762,7 @@ AllocSetAlloc(MemoryContext context, Size size)
 		if (block == NULL)
 			return NULL;
 
-		context->mem_allocated += blksize;
+		set->memAllocated += blksize;
 
 		block->aset = set;
 		block->freeptr = block->endptr = ((char *) block) + blksize;
@@ -805,6 +809,7 @@ AllocSetAlloc(MemoryContext context, Size size)
 		/* Disallow external access to private part of chunk header. */
 		VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
 
+		set->nChunks++;
 		return AllocChunkGetPointer(chunk);
 	}
 
@@ -844,6 +849,8 @@ AllocSetAlloc(MemoryContext context, Size size)
 		/* Disallow external access to private part of chunk header. */
 		VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
 
+		/* chunk already existed; don't increment nChunks */
+
 		return AllocChunkGetPointer(chunk);
 	}
 
@@ -906,6 +913,7 @@ AllocSetAlloc(MemoryContext context, Size size)
 #endif
 				chunk->aset = (void *) set->freelist[a_fidx];
 				set->freelist[a_fidx] = chunk;
+				set->nChunks++;
 			}
 
 			/* Mark that we need to create a new block */
@@ -955,7 +963,7 @@ AllocSetAlloc(MemoryContext context, Size size)
 		if (block == NULL)
 			return NULL;
 
-		context->mem_allocated += blksize;
+		set->memAllocated += blksize;
 
 		block->aset = set;
 		block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
@@ -1005,6 +1013,7 @@ AllocSetAlloc(MemoryContext context, Size size)
 	/* Disallow external access to private part of chunk header. */
 	VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
 
+	set->nChunks++;
 	return AllocChunkGetPointer(chunk);
 }
 
@@ -1058,7 +1067,8 @@ AllocSetFree(MemoryContext context, void *pointer)
 		if (block->next)
 			block->next->prev = block->prev;
 
-		context->mem_allocated -= block->endptr - ((char*) block);
+		set->memAllocated -= block->endptr - ((char*) block);
+		set->nChunks--;
 
 #ifdef CLOBBER_FREED_MEMORY
 		wipe_mem(block, block->freeptr - ((char *) block));
@@ -1161,8 +1171,8 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
 		}
 
 		/* updated separately, not to underflow when (oldblksize > blksize) */
-		context->mem_allocated -= oldblksize;
-		context->mem_allocated += blksize;
+		set->memAllocated -= oldblksize;
+		set->memAllocated += blksize;
 
 		block->freeptr = block->endptr = ((char *) block) + blksize;
 
@@ -1356,65 +1366,53 @@ AllocSetIsEmpty(MemoryContext context)
 }
 
 /*
- * AllocSetStats
- *		Compute stats about memory consumption of an allocset.
- *
- * 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.
+ * AllocSetGetCounters
+ *		Compute and return memory consumption counters. Specify the needed
+ *		counters by setting the appropriate flags.
  */
 static void
-AllocSetStats(MemoryContext context,
-			  MemoryStatsPrintFunc printfunc, void *passthru,
-			  MemoryContextCounters *totals)
+AllocSetGetCounters(MemoryContext context, uint32 flags,
+					MemoryContextCounters *counters)
 {
-	AllocSet	set = (AllocSet) context;
-	Size		nblocks = 0;
+	AllocSet	set		   = (AllocSet) context;
+	Size		nblocks	   = 0;
 	Size		freechunks = 0;
-	Size		totalspace;
-	Size		freespace = 0;
-	AllocBlock	block;
-	int			fidx;
+	Size		freespace  = 0;
 
-	/* Include context header in totalspace */
-	totalspace = MAXALIGN(sizeof(AllocSetContext));
-
-	for (block = set->blocks; block != NULL; block = block->next)
-	{
-		nblocks++;
-		totalspace += block->endptr - ((char *) block);
-		freespace += block->endptr - block->freeptr;
-	}
-	for (fidx = 0; fidx < ALLOCSET_NUM_FREELISTS; fidx++)
+	if (flags & (MCXT_COUNT_NBLOCKS | MCXT_COUNT_FREESPACE))
 	{
-		AllocChunk	chunk;
+		AllocBlock	block;
 
-		for (chunk = set->freelist[fidx]; chunk != NULL;
-			 chunk = (AllocChunk) chunk->aset)
+		for (block = set->blocks; block != NULL; block = block->next)
 		{
-			freechunks++;
-			freespace += chunk->size + ALLOC_CHUNKHDRSZ;
+			nblocks++;
+			freespace += block->endptr - block->freeptr;
 		}
 	}
 
-	if (printfunc)
+	if (flags & (MCXT_COUNT_FREECHUNKS | MCXT_COUNT_FREESPACE))
 	{
-		char		stats_string[200];
+		int		fidx;
 
-		snprintf(stats_string, sizeof(stats_string),
-				 "%zu total in %zd blocks; %zu free (%zd chunks); %zu used",
-				 totalspace, nblocks, freespace, freechunks,
-				 totalspace - freespace);
-		printfunc(context, passthru, stats_string);
-	}
+		for (fidx = 0; fidx < ALLOCSET_NUM_FREELISTS; fidx++)
+		{
+			AllocChunk	chunk;
 
-	if (totals)
-	{
-		totals->nblocks += nblocks;
-		totals->freechunks += freechunks;
-		totals->totalspace += totalspace;
-		totals->freespace += freespace;
+			for (chunk = set->freelist[fidx]; chunk != NULL;
+				 chunk = (AllocChunk) chunk->aset)
+			{
+				freechunks++;
+				freespace += chunk->size + ALLOC_CHUNKHDRSZ;
+			}
+		}
 	}
+
+	counters->nblocks = nblocks;
+	counters->nchunks = set->nChunks;
+	counters->freechunks = freechunks;
+	counters->totalspace = set->memAllocated;
+	counters->freespace = freespace;
+	counters->newspace = set->blocks->endptr - set->blocks->freeptr;
 }
 
 
@@ -1436,6 +1434,7 @@ AllocSetCheck(MemoryContext context)
 	AllocBlock	prevblock;
 	AllocBlock	block;
 	Size		total_allocated = 0;
+	uint64		total_nchunks = 0;
 
 	for (prevblock = NULL, block = set->blocks;
 		 block != NULL;
@@ -1529,6 +1528,7 @@ AllocSetCheck(MemoryContext context)
 
 			blk_data += chsize;
 			nchunks++;
+			total_nchunks++;
 
 			bpoz += ALLOC_CHUNKHDRSZ + chsize;
 		}
@@ -1538,7 +1538,8 @@ AllocSetCheck(MemoryContext context)
 				 name, block);
 	}
 
-	Assert(total_allocated == context->mem_allocated);
+	Assert(total_allocated == set->memAllocated);
+	Assert(total_nchunks == set->nChunks);
 }
 
 #endif							/* MEMORY_CONTEXT_CHECKING */
diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c
index 56651d06931..f1a989dd1e6 100644
--- a/src/backend/utils/mmgr/generation.c
+++ b/src/backend/utils/mmgr/generation.c
@@ -61,6 +61,7 @@ typedef struct GenerationContext
 
 	/* Generational context parameters */
 	Size		blockSize;		/* standard block size */
+	Size		memAllocated;	/* track memory allocated for this context */
 
 	GenerationBlock *block;		/* current (most recently allocated) block */
 	dlist_head	blocks;			/* list of blocks */
@@ -153,9 +154,8 @@ static void GenerationReset(MemoryContext context);
 static void GenerationDelete(MemoryContext context);
 static Size GenerationGetChunkSpace(MemoryContext context, void *pointer);
 static bool GenerationIsEmpty(MemoryContext context);
-static void GenerationStats(MemoryContext context,
-							MemoryStatsPrintFunc printfunc, void *passthru,
-							MemoryContextCounters *totals);
+static void GenerationGetCounters(MemoryContext context, uint32 flags,
+								  MemoryContextCounters *counters);
 
 #ifdef MEMORY_CONTEXT_CHECKING
 static void GenerationCheck(MemoryContext context);
@@ -172,7 +172,7 @@ static const MemoryContextMethods GenerationMethods = {
 	GenerationDelete,
 	GenerationGetChunkSpace,
 	GenerationIsEmpty,
-	GenerationStats
+	GenerationGetCounters
 #ifdef MEMORY_CONTEXT_CHECKING
 	,GenerationCheck
 #endif
@@ -258,6 +258,7 @@ GenerationContextCreate(MemoryContext parent,
 
 	/* Fill in GenerationContext-specific header fields */
 	set->blockSize = blockSize;
+	set->memAllocated = 0;
 	set->block = NULL;
 	dlist_init(&set->blocks);
 
@@ -297,7 +298,7 @@ GenerationReset(MemoryContext context)
 
 		dlist_delete(miter.cur);
 
-		context->mem_allocated -= block->blksize;
+		set->memAllocated -= block->blksize;
 
 #ifdef CLOBBER_FREED_MEMORY
 		wipe_mem(block, block->blksize);
@@ -354,7 +355,7 @@ GenerationAlloc(MemoryContext context, Size size)
 		if (block == NULL)
 			return NULL;
 
-		context->mem_allocated += blksize;
+		set->memAllocated += blksize;
 
 		/* block with a single (used) chunk */
 		block->blksize = blksize;
@@ -411,7 +412,7 @@ GenerationAlloc(MemoryContext context, Size size)
 		if (block == NULL)
 			return NULL;
 
-		context->mem_allocated += blksize;
+		set->memAllocated += blksize;
 
 		block->blksize = blksize;
 		block->nchunks = 0;
@@ -528,7 +529,7 @@ GenerationFree(MemoryContext context, void *pointer)
 	if (set->block == block)
 		set->block = NULL;
 
-	context->mem_allocated -= block->blksize;
+	set->memAllocated -= block->blksize;
 	free(block);
 }
 
@@ -679,61 +680,43 @@ GenerationIsEmpty(MemoryContext context)
 }
 
 /*
- * GenerationStats
- *		Compute stats about memory consumption of a Generation 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.
- *
- * XXX freespace only accounts for empty space at the end of the block, not
- * space of freed chunks (which is unknown).
+ * GenerationGetCounters
+ *		Compute and return memory consumption counters. Specify the needed
+ *		counters by setting the appropriate flags.
  */
 static void
-GenerationStats(MemoryContext context,
-				MemoryStatsPrintFunc printfunc, void *passthru,
-				MemoryContextCounters *totals)
+GenerationGetCounters(MemoryContext context, uint32 flags,
+					  MemoryContextCounters *counters)
 {
-	GenerationContext *set = (GenerationContext *) context;
-	Size		nblocks = 0;
-	Size		nchunks = 0;
-	Size		nfreechunks = 0;
-	Size		totalspace;
-	Size		freespace = 0;
-	dlist_iter	iter;
-
-	/* Include context header in totalspace */
-	totalspace = MAXALIGN(sizeof(GenerationContext));
-
-	dlist_foreach(iter, &set->blocks)
+	GenerationContext		*set	  = (GenerationContext *) context;
+	uint64					 nblocks = 0;
+	uint64					 nchunks = 0;
+	uint64					 freechunks = 0;
+	Size					 freespace = 0;
+
+	if (flags & (MCXT_COUNT_NBLOCKS | MCXT_COUNT_NCHUNKS |
+				 MCXT_COUNT_FREECHUNKS | MCXT_COUNT_FREESPACE))
 	{
-		GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
+		dlist_iter	iter;
 
-		nblocks++;
-		nchunks += block->nchunks;
-		nfreechunks += block->nfree;
-		totalspace += block->blksize;
-		freespace += (block->endptr - block->freeptr);
-	}
-
-	if (printfunc)
-	{
-		char		stats_string[200];
+		dlist_foreach(iter, &set->blocks)
+		{
+			GenerationBlock *block = dlist_container(
+				GenerationBlock, node, iter.cur);
 
-		snprintf(stats_string, sizeof(stats_string),
-				 "%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);
+			nblocks++;
+			nchunks += block->nchunks;
+			freechunks += block->nfree;
+			freespace += (block->endptr - block->freeptr);
+		}
 	}
 
-	if (totals)
-	{
-		totals->nblocks += nblocks;
-		totals->freechunks += nfreechunks;
-		totals->totalspace += totalspace;
-		totals->freespace += freespace;
-	}
+	counters->nblocks = nblocks;
+	counters->nchunks = nchunks;
+	counters->freechunks = freechunks;
+	counters->totalspace = set->memAllocated;
+	counters->freespace = freespace;
+	counters->newspace = set->block->endptr - set->block->freeptr;
 }
 
 
@@ -844,7 +827,7 @@ GenerationCheck(MemoryContext context)
 				 name, nfree, block, block->nfree);
 	}
 
-	Assert(total_allocated == context->mem_allocated);
+	Assert(total_allocated == gen->memAllocated);
 }
 
 #endif							/* MEMORY_CONTEXT_CHECKING */
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 9e24fec72d6..1708413c46c 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -56,7 +56,7 @@ static void MemoryContextCallResetCallbacks(MemoryContext context);
 static void MemoryContextStatsInternal(MemoryContext context, int level,
 									   bool print, int max_children,
 									   MemoryContextCounters *totals);
-static void MemoryContextStatsPrint(MemoryContext context, void *passthru,
+static void MemoryContextStatsPrint(MemoryContext context, int level,
 									const char *stats_string);
 
 /*
@@ -463,27 +463,39 @@ MemoryContextIsEmpty(MemoryContext context)
 }
 
 /*
- * Find the memory allocated to blocks for this memory context. If recurse is
- * true, also include children.
+ * MemoryContextCount
+ *		Return statistics about this memory context, optionally recursing to
+ *		children. Flags are defined in memnodes.h and specify which statistics
+ *		are required.
  */
-Size
-MemoryContextMemAllocated(MemoryContext context, bool recurse)
+void
+MemoryContextGetCounters(MemoryContext context, uint32 flags, bool recurse,
+						 MemoryContextCounters *counters)
 {
-	Size	total = context->mem_allocated;
-
 	AssertArg(MemoryContextIsValid(context));
 
+	context->methods->get_counters(context, flags, counters);
+
 	if (recurse)
 	{
-		MemoryContext child = context->firstchild;
+		MemoryContext                   child;
 
 		for (child = context->firstchild;
 			 child != NULL;
 			 child = child->nextchild)
-			total += MemoryContextMemAllocated(child, true);
-	}
+		{
+			MemoryContextCounters child_counters;
+
+			child->methods->get_counters(child, flags, &child_counters);
 
-	return total;
+			counters->nblocks += child_counters.nblocks;
+			counters->nchunks += child_counters.nchunks;
+			counters->freechunks += child_counters.freechunks;
+			counters->totalspace += child_counters.totalspace;
+			counters->freespace += child_counters.freespace;
+			counters->newspace += child_counters.newspace;
+		}
+	}
 }
 
 /*
@@ -516,9 +528,10 @@ MemoryContextStatsDetail(MemoryContext context, int max_children)
 	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 total: %zu bytes in %zd blocks (%zd chunks); %zu free (%zd chunks); %zu used\n",
 			grand_totals.totalspace, grand_totals.nblocks,
-			grand_totals.freespace, grand_totals.freechunks,
+			grand_totals.nchunks, grand_totals.freespace,
+			grand_totals.freechunks,
 			grand_totals.totalspace - grand_totals.freespace);
 }
 
@@ -534,24 +547,42 @@ MemoryContextStatsInternal(MemoryContext context, int level,
 						   bool print, int max_children,
 						   MemoryContextCounters *totals)
 {
-	MemoryContextCounters local_totals;
+	MemoryContextCounters excess_summary = {0};
+	MemoryContextCounters current;
 	MemoryContext child;
 	int			ichild;
 
+
 	AssertArg(MemoryContextIsValid(context));
 
 	/* Examine the context itself */
-	context->methods->stats(context,
-							print ? MemoryContextStatsPrint : NULL,
-							(void *) &level,
-							totals);
+	context->methods->get_counters(context, MCXT_COUNT_ALL, &current);
+
+	if (print)
+	{
+		char		stats_string[200];
+		snprintf(stats_string, sizeof(stats_string),
+				 "%zu total in %zd blocks (%zd chunks); %zu free (%zd chunks); %zu used",
+				 current.totalspace, current.nblocks, current.nchunks,
+				 current.freespace, current.freechunks,
+				 current.totalspace - current.freespace);
+		MemoryContextStatsPrint(context, level, stats_string);
+	}
+
+	if (totals)
+	{
+		totals->nblocks += current.nblocks;
+		totals->nchunks += current.nchunks;
+		totals->freechunks += current.freechunks;
+		totals->totalspace += current.totalspace;
+		totals->freespace += current.freespace;
+		totals->newspace += current.newspace;
+	}
 
 	/*
 	 * 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++)
@@ -563,7 +594,7 @@ MemoryContextStatsInternal(MemoryContext context, int level,
 		else
 			MemoryContextStatsInternal(child, level + 1,
 									   false, max_children,
-									   &local_totals);
+									   &excess_summary);
 	}
 
 	/* Deal with excess children */
@@ -578,35 +609,33 @@ MemoryContextStatsInternal(MemoryContext context, int level,
 			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);
+					excess_summary.totalspace,
+					excess_summary.nblocks,
+					excess_summary.freespace,
+					excess_summary.freechunks,
+					excess_summary.totalspace - excess_summary.freespace);
 		}
 
 		if (totals)
 		{
-			totals->nblocks += local_totals.nblocks;
-			totals->freechunks += local_totals.freechunks;
-			totals->totalspace += local_totals.totalspace;
-			totals->freespace += local_totals.freespace;
+			totals->nblocks += excess_summary.nblocks;
+			totals->nchunks += excess_summary.nchunks;
+			totals->freechunks += excess_summary.freechunks;
+			totals->totalspace += excess_summary.totalspace;
+			totals->freespace += excess_summary.freespace;
+			totals->newspace += excess_summary.newspace;
 		}
 	}
 }
 
 /*
  * MemoryContextStatsPrint
- *		Print callback used by MemoryContextStatsInternal
- *
- * For now, the passthru pointer just points to "int level"; later we might
- * make that more complicated.
+ *		Print function used by MemoryContextStatsInternal
  */
 static void
-MemoryContextStatsPrint(MemoryContext context, void *passthru,
+MemoryContextStatsPrint(MemoryContext context, int level,
 						const char *stats_string)
 {
-	int			level = *(int *) passthru;
 	const char *name = context->name;
 	const char *ident = context->ident;
 	int			i;
@@ -760,7 +789,6 @@ MemoryContextCreate(MemoryContext node,
 	node->methods = methods;
 	node->parent = parent;
 	node->firstchild = NULL;
-	node->mem_allocated = 0;
 	node->prevchild = NULL;
 	node->name = name;
 	node->ident = NULL;
diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c
index c928476c479..acab22ffa49 100644
--- a/src/backend/utils/mmgr/slab.c
+++ b/src/backend/utils/mmgr/slab.c
@@ -67,6 +67,7 @@ typedef struct SlabContext
 	Size		fullChunkSize;	/* chunk size including header and alignment */
 	Size		blockSize;		/* block size */
 	Size		headerSize;		/* allocated size of context header */
+	Size		memAllocated;	/* track memory allocated for this context */
 	int			chunksPerBlock; /* number of chunks per block */
 	int			minFreeChunks;	/* min number of free chunks in any block */
 	int			nblocks;		/* number of blocks allocated */
@@ -133,9 +134,8 @@ static void SlabReset(MemoryContext context);
 static void SlabDelete(MemoryContext context);
 static Size SlabGetChunkSpace(MemoryContext context, void *pointer);
 static bool SlabIsEmpty(MemoryContext context);
-static void SlabStats(MemoryContext context,
-					  MemoryStatsPrintFunc printfunc, void *passthru,
-					  MemoryContextCounters *totals);
+static void SlabGetCounters(MemoryContext context, uint32 flags,
+							MemoryContextCounters *counters);
 #ifdef MEMORY_CONTEXT_CHECKING
 static void SlabCheck(MemoryContext context);
 #endif
@@ -151,7 +151,7 @@ static const MemoryContextMethods SlabMethods = {
 	SlabDelete,
 	SlabGetChunkSpace,
 	SlabIsEmpty,
-	SlabStats
+	SlabGetCounters
 #ifdef MEMORY_CONTEXT_CHECKING
 	,SlabCheck
 #endif
@@ -262,6 +262,7 @@ SlabContextCreate(MemoryContext parent,
 	slab->fullChunkSize = fullChunkSize;
 	slab->blockSize = blockSize;
 	slab->headerSize = headerSize;
+	slab->memAllocated = 0;
 	slab->chunksPerBlock = chunksPerBlock;
 	slab->minFreeChunks = 0;
 	slab->nblocks = 0;
@@ -322,14 +323,14 @@ SlabReset(MemoryContext context)
 #endif
 			free(block);
 			slab->nblocks--;
-			context->mem_allocated -= slab->blockSize;
+			slab->memAllocated -= slab->blockSize;
 		}
 	}
 
 	slab->minFreeChunks = 0;
 
 	Assert(slab->nblocks == 0);
-	Assert(context->mem_allocated == 0);
+	Assert(slab->memAllocated == 0);
 }
 
 /*
@@ -407,7 +408,7 @@ SlabAlloc(MemoryContext context, Size size)
 
 		slab->minFreeChunks = slab->chunksPerBlock;
 		slab->nblocks += 1;
-		context->mem_allocated += slab->blockSize;
+		slab->memAllocated += slab->blockSize;
 	}
 
 	/* grab the block from the freelist (even the new block is there) */
@@ -501,7 +502,7 @@ SlabAlloc(MemoryContext context, Size size)
 
 	SlabAllocInfo(slab, chunk);
 
-	Assert(slab->nblocks * slab->blockSize == context->mem_allocated);
+	Assert(slab->nblocks * slab->blockSize == slab->memAllocated);
 
 	return SlabChunkGetPointer(chunk);
 }
@@ -578,13 +579,13 @@ SlabFree(MemoryContext context, void *pointer)
 	{
 		free(block);
 		slab->nblocks--;
-		context->mem_allocated -= slab->blockSize;
+		slab->memAllocated -= slab->blockSize;
 	}
 	else
 		dlist_push_head(&slab->freelist[block->nfree], &block->node);
 
 	Assert(slab->nblocks >= 0);
-	Assert(slab->nblocks * slab->blockSize == context->mem_allocated);
+	Assert(slab->nblocks * slab->blockSize == slab->memAllocated);
 }
 
 /*
@@ -645,61 +646,48 @@ SlabIsEmpty(MemoryContext context)
 }
 
 /*
- * SlabStats
- *		Compute stats about memory consumption of a Slab 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.
+ * SlabGetCounters
+ *		Compute and return memory consumption counters. Specify the needed
+ *		counters by setting the appropriate flags.
  */
 static void
-SlabStats(MemoryContext context,
-		  MemoryStatsPrintFunc printfunc, void *passthru,
-		  MemoryContextCounters *totals)
+SlabGetCounters(MemoryContext context, uint32 flags,
+				MemoryContextCounters *counters)
 {
-	SlabContext *slab = castNode(SlabContext, context);
-	Size		nblocks = 0;
-	Size		freechunks = 0;
-	Size		totalspace;
-	Size		freespace = 0;
-	int			i;
-
-	/* Include context header in totalspace */
-	totalspace = slab->headerSize;
-
-	for (i = 0; i <= slab->chunksPerBlock; i++)
+	SlabContext				*slab	  = castNode(SlabContext, context);
+	uint64					 nblocks = 0;
+	uint64					 nchunks = 0;
+	uint64					 freechunks = 0;
+	Size					 freespace = 0;
+
+	if (flags & (MCXT_COUNT_NBLOCKS | MCXT_COUNT_NCHUNKS |
+				 MCXT_COUNT_FREECHUNKS | MCXT_COUNT_FREESPACE))
 	{
-		dlist_iter	iter;
+		int	i;
 
-		dlist_foreach(iter, &slab->freelist[i])
+		for (i = 0; i <= slab->chunksPerBlock; i++)
 		{
-			SlabBlock  *block = dlist_container(SlabBlock, node, iter.cur);
+			dlist_iter	iter;
 
-			nblocks++;
-			totalspace += slab->blockSize;
-			freespace += slab->fullChunkSize * block->nfree;
-			freechunks += block->nfree;
-		}
-	}
-
-	if (printfunc)
-	{
-		char		stats_string[200];
+			dlist_foreach(iter, &slab->freelist[i])
+			{
+				SlabBlock  *block = dlist_container(SlabBlock, node, iter.cur);
 
-		snprintf(stats_string, sizeof(stats_string),
-				 "%zu total in %zd blocks; %zu free (%zd chunks); %zu used",
-				 totalspace, nblocks, freespace, freechunks,
-				 totalspace - freespace);
-		printfunc(context, passthru, stats_string);
+				nblocks++;
+				nchunks += slab->chunksPerBlock;
+				freespace += slab->fullChunkSize * block->nfree;
+				freechunks += block->nfree;
+			}
+		}
 	}
 
-	if (totals)
-	{
-		totals->nblocks += nblocks;
-		totals->freechunks += freechunks;
-		totals->totalspace += totalspace;
-		totals->freespace += freespace;
-	}
+	counters->nblocks = nblocks;
+	counters->nchunks = nchunks;
+	counters->freechunks = freechunks;
+	counters->totalspace = slab->memAllocated;
+	counters->freespace = freespace;
+	/* new memory is already sliced into chunks, so newspace is always 0 */
+	counters->newspace = 0;
 }
 
 
@@ -804,7 +792,7 @@ SlabCheck(MemoryContext context)
 		}
 	}
 
-	Assert(slab->nblocks * slab->blockSize == context->mem_allocated);
+	Assert(slab->nblocks * slab->blockSize == slab->memAllocated);
 }
 
 #endif							/* MEMORY_CONTEXT_CHECKING */
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index c9f2bbcb367..798854da99d 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -25,15 +25,30 @@
  * 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.
+ *
+ * The 'newspace' counter represents memory that is allocated by malloc, but
+ * not touched yet. This can be significant for context types that malloc
+ * large blocks of memory in anticipation of chunks that have not yet been
+ * requested.
  */
 typedef struct MemoryContextCounters
 {
 	Size		nblocks;		/* Total number of malloc blocks */
+	Size		nchunks;		/* Total number of chunks (used+free) */
 	Size		freechunks;		/* Total number of free chunks */
 	Size		totalspace;		/* Total bytes requested from malloc */
 	Size		freespace;		/* The unused portion of totalspace */
+	Size		newspace;		/* Allocated but never held any chunks */
 } MemoryContextCounters;
 
+#define MCXT_COUNT_NBLOCKS		(1 << 0)
+#define MCXT_COUNT_NCHUNKS		(1 << 1)
+#define MCXT_COUNT_FREECHUNKS	(1 << 2)
+#define MCXT_COUNT_TOTALSPACE	(1 << 3)
+#define MCXT_COUNT_FREESPACE	(1 << 4)
+#define MCXT_COUNT_NEWSPACE		(1 << 5)
+#define MCXT_COUNT_ALL			(~0)
+
 /*
  * MemoryContext
  *		A logical context in which memory allocations occur.
@@ -51,9 +66,6 @@ typedef struct MemoryContextCounters
  * to the context struct rather than the struct type itself.
  */
 
-typedef void (*MemoryStatsPrintFunc) (MemoryContext context, void *passthru,
-									  const char *stats_string);
-
 typedef struct MemoryContextMethods
 {
 	void	   *(*alloc) (MemoryContext context, Size size);
@@ -64,9 +76,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,
-						  MemoryStatsPrintFunc printfunc, void *passthru,
-						  MemoryContextCounters *totals);
+	void		(*get_counters) (MemoryContext context, uint32 flags,
+								 MemoryContextCounters *counters);
 #ifdef MEMORY_CONTEXT_CHECKING
 	void		(*check) (MemoryContext context);
 #endif
@@ -79,7 +90,6 @@ typedef struct MemoryContextData
 	/* these two fields are placed here to minimize alignment wastage: */
 	bool		isReset;		/* T = no space alloced since last reset */
 	bool		allowInCritSection; /* allow palloc in critical section */
-	Size		mem_allocated;	/* track memory allocated for this context */
 	const MemoryContextMethods *methods;	/* virtual function table */
 	MemoryContext parent;		/* NULL if no parent (toplevel context) */
 	MemoryContext firstchild;	/* head of linked list of children */
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 909bc2e9888..e787e43b3fb 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -82,7 +82,9 @@ extern void MemoryContextSetParent(MemoryContext context,
 extern Size GetMemoryChunkSpace(void *pointer);
 extern MemoryContext MemoryContextGetParent(MemoryContext context);
 extern bool MemoryContextIsEmpty(MemoryContext context);
-extern Size MemoryContextMemAllocated(MemoryContext context, bool recurse);
+extern void MemoryContextGetCounters(MemoryContext context, uint32 flags,
+									 bool recurse,
+									 MemoryContextCounters *counters);
 extern void MemoryContextStats(MemoryContext context);
 extern void MemoryContextStatsDetail(MemoryContext context, int max_children);
 extern void MemoryContextAllowInCriticalSection(MemoryContext context,

Reply via email to