diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index bdc34cf94e8..a0b692bf1e9 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -504,7 +504,22 @@
levelint4
- Distance from TopMemoryContext in context tree
+ The 1-based level of the context in the memory context hierarchy. The
+ level of a context also shows the position of that context in the
+ path column.
+
+
+
+
+
+ pathint4[]
+
+
+ Array of transient numerical identifiers to describe the memory
+ context hierarchy. The first element is for
+ TopMemoryContext, subsequent elements contain
+ intermediate parents and the final element contains the identifier for
+ the current context.
@@ -561,6 +576,29 @@
read only by superusers or roles with the privileges of the
pg_read_all_stats role.
+
+
+ Since memory contexts are created and destroyed during the running of a
+ query, the identifiers stored in the path column
+ can be unstable between multiple invocations of the view in the same query.
+ The example below demonstrates an effective usage of this column and
+ calculates the total number of bytes used by
+ CacheMemoryContext and all of its children:
+
+
+WITH memory_contexts AS (
+ SELECT * FROM pg_backend_memory_contexts
+)
+SELECT sum(c1.total_bytes)
+FROM memory_contexts c1, memory_contexts c2
+WHERE c2.name = 'CacheMemoryContext'
+AND c1.path[c2.level] = c2.path[c2.level];
+
+
+ The Common Table Expression is used
+ to ensure the context IDs in the path column
+ match between both evaluations of the view.
+
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index 10859414848..199e68c1ae5 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -19,7 +19,9 @@
#include "mb/pg_wchar.h"
#include "storage/proc.h"
#include "storage/procarray.h"
+#include "utils/array.h"
#include "utils/builtins.h"
+#include "utils/hsearch.h"
/* ----------
* The max bytes for showing identifiers of MemoryContext.
@@ -27,47 +29,107 @@
*/
#define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE 1024
+/*
+ * MemoryContextId
+ * Used for storage of transient identifiers for
+ * pg_get_backend_memory_contexts.
+ */
+typedef struct MemoryContextId
+{
+ MemoryContext context;
+ int context_id;
+} MemoryContextId;
+
+/*
+ * get_memory_context_name_and_ident
+ * Populate *name and *ident from the name and ident from 'context'.
+ */
+static void
+get_memory_context_name_and_ident(MemoryContext context, const char **const name,
+ const char **const ident)
+{
+ *name = context->name;
+ *ident = context->ident;
+
+ /*
+ * To be consistent with logging output, we label dynahash contexts with
+ * just the hash table name as with MemoryContextStatsPrint().
+ */
+ if (ident && strcmp(*name, "dynahash") == 0)
+ {
+ *name = *ident;
+ *ident = NULL;
+ }
+}
+
+/*
+ * int_list_to_array
+ * Convert an IntList to an array of INT4OIDs.
+ */
+static Datum
+int_list_to_array(const List *list)
+{
+ Datum *datum_array;
+ int length;
+ ArrayType *result_array;
+
+ length = list_length(list);
+ datum_array = (Datum *) palloc(length * sizeof(Datum));
+
+ foreach_int(i, list)
+ datum_array[foreach_current_index(i)] = Int32GetDatum(i);
+
+ result_array = construct_array_builtin(datum_array, length, INT4OID);
+
+ return PointerGetDatum(result_array);
+}
+
/*
* PutMemoryContextsStatsTupleStore
- * One recursion level for pg_get_backend_memory_contexts.
+ * Add details for the given MemoryContext to 'tupstore'.
*/
static void
PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
TupleDesc tupdesc, MemoryContext context,
- const char *parent, int level)
+ HTAB *context_id_lookup)
{
-#define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS 10
+#define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS 11
Datum values[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
bool nulls[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
MemoryContextCounters stat;
- MemoryContext child;
+ List *path = NIL;
const char *name;
const char *ident;
const char *type;
Assert(MemoryContextIsValid(context));
- name = context->name;
- ident = context->ident;
-
/*
- * To be consistent with logging output, we label dynahash contexts with
- * just the hash table name as with MemoryContextStatsPrint().
+ * Figure out the transient context_id of this context and each of its
+ * ancestors.
*/
- if (ident && strcmp(name, "dynahash") == 0)
+ for (MemoryContext cur = context; cur != NULL; cur = cur->parent)
{
- name = ident;
- ident = NULL;
+ MemoryContextId *entry;
+ bool found;
+
+ entry = hash_search(context_id_lookup, &cur, HASH_FIND, &found);
+
+ if (!found)
+ elog(ERROR, "hash table corrupted");
+ path = lcons_int(entry->context_id, path);
}
/* Examine the context itself */
memset(&stat, 0, sizeof(stat));
- (*context->methods->stats) (context, NULL, (void *) &level, &stat, true);
+ (*context->methods->stats) (context, NULL, NULL, &stat, true);
memset(values, 0, sizeof(values));
memset(nulls, 0, sizeof(nulls));
+ get_memory_context_name_and_ident(context, &name, &ident);
+
if (name)
values[0] = CStringGetTextDatum(name);
else
@@ -92,8 +154,15 @@ PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
else
nulls[1] = true;
- if (parent)
- values[2] = CStringGetTextDatum(parent);
+ if (context->parent)
+ {
+ const char *parent_name,
+ *parent_ident;
+
+ get_memory_context_name_and_ident(context->parent, &parent_name,
+ &parent_ident);
+ values[2] = CStringGetTextDatum(parent_name);
+ }
else
nulls[2] = true;
@@ -117,19 +186,16 @@ PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
}
values[3] = CStringGetTextDatum(type);
- values[4] = Int32GetDatum(level);
- values[5] = Int64GetDatum(stat.totalspace);
- values[6] = Int64GetDatum(stat.nblocks);
- values[7] = Int64GetDatum(stat.freespace);
- values[8] = Int64GetDatum(stat.freechunks);
- values[9] = Int64GetDatum(stat.totalspace - stat.freespace);
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ values[4] = Int32GetDatum(list_length(path)); /* level */
+ values[5] = int_list_to_array(path);
+ values[6] = Int64GetDatum(stat.totalspace);
+ values[7] = Int64GetDatum(stat.nblocks);
+ values[8] = Int64GetDatum(stat.freespace);
+ values[9] = Int64GetDatum(stat.freechunks);
+ values[10] = Int64GetDatum(stat.totalspace - stat.freespace);
- for (child = context->firstchild; child != NULL; child = child->nextchild)
- {
- PutMemoryContextsStatsTupleStore(tupstore, tupdesc,
- child, name, level + 1);
- }
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ list_free(path);
}
/*
@@ -140,10 +206,66 @@ Datum
pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ int context_id;
+ List *contexts;
+ HASHCTL ctl;
+ HTAB *context_id_lookup;
+
+ ctl.keysize = sizeof(MemoryContext);
+ ctl.entrysize = sizeof(MemoryContextId);
+ ctl.hcxt = CurrentMemoryContext;
+
+ context_id_lookup = hash_create("pg_get_backend_memory_contexts",
+ 256,
+ &ctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
InitMaterializedSRF(fcinfo, 0);
- PutMemoryContextsStatsTupleStore(rsinfo->setResult, rsinfo->setDesc,
- TopMemoryContext, NULL, 0);
+
+ /*
+ * Here we use a non-recursive algorithm to visit all MemoryContexts
+ * starting with TopMemoryContext. The reason we avoid using a recursive
+ * algorithm is because we want to assign the context_id breadth-first.
+ * I.e. all contexts at level 1 are assigned IDs before contexts at level
+ * 2. Because contexts closer to TopMemoryContext are less likely to
+ * change, this makes the assigned context_id more stable. Otherwise, if
+ * the first child of TopMemoryContext obtained an additional grandchild,
+ * the context_id for the second child of TopMemoryContext would change.
+ */
+ contexts = list_make1(TopMemoryContext);
+
+ /* TopMemoryContext will always have a context_id of 1 */
+ context_id = 1;
+
+ foreach_ptr(MemoryContextData, cur, contexts)
+ {
+ MemoryContextId *entry;
+ bool found;
+
+ /*
+ * Record the context_id that we've assigned to each MemoryContext.
+ * PutMemoryContextsStatsTupleStore needs this to populate the "path"
+ * column with the parent context_ids.
+ */
+ entry = (MemoryContextId *) hash_search(context_id_lookup, &cur,
+ HASH_ENTER, &found);
+ entry->context_id = context_id++;
+ Assert(!found);
+
+ PutMemoryContextsStatsTupleStore(rsinfo->setResult,
+ rsinfo->setDesc,
+ cur,
+ context_id_lookup);
+
+ /*
+ * Append all children onto the contexts list so they're processed by
+ * subsequent iterations.
+ */
+ for (MemoryContext c = cur->firstchild; c != NULL; c = c->nextchild)
+ contexts = lappend(contexts, c);
+ }
+
+ hash_destroy(context_id_lookup);
return (Datum) 0;
}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 3254e7ab929..4c88ea1034f 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202407111
+#define CATALOG_VERSION_NO 202407251
#endif
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 73d9cf85826..d14a94b9873 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8290,9 +8290,9 @@
proname => 'pg_get_backend_memory_contexts', prorows => '100',
proretset => 't', provolatile => 'v', proparallel => 'r',
prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,text,text,text,int4,int8,int8,int8,int8,int8}',
- proargmodes => '{o,o,o,o,o,o,o,o,o,o}',
- proargnames => '{name, ident, parent, type, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes}',
+ proallargtypes => '{text,text,text,text,int4,_int4,int8,int8,int8,int8,int8}',
+ proargmodes => '{o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{name, ident, parent, type, level, path, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes}',
prosrc => 'pg_get_backend_memory_contexts' },
# logging memory contexts of the specified backend
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index c4c9fd3e3e1..addfbb1ccdf 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -128,8 +128,8 @@ typedef struct MemoryContextData
MemoryContext firstchild; /* head of linked list of children */
MemoryContext prevchild; /* previous child of same parent */
MemoryContext nextchild; /* next child of same parent */
- const char *name; /* context name (just for debugging) */
- const char *ident; /* context ID if any (just for debugging) */
+ const char *name; /* context name */
+ const char *ident; /* context ID if any */
MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */
} MemoryContextData;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 4c789279e5e..52012806699 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1308,12 +1308,13 @@ pg_backend_memory_contexts| SELECT name,
parent,
type,
level,
+ path,
total_bytes,
total_nblocks,
free_bytes,
free_chunks,
used_bytes
- FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, type, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
+ FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, type, level, path, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
pg_config| SELECT name,
setting
FROM pg_config() pg_config(name, setting);
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 729620de13c..84b3b64b490 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -22,10 +22,10 @@ select count(*) >= 0 as ok from pg_available_extensions;
-- The entire output of pg_backend_memory_contexts is not stable,
-- we test only the existence and basic condition of TopMemoryContext.
select type, name, ident, parent, level, total_bytes >= free_bytes
- from pg_backend_memory_contexts where level = 0;
+ from pg_backend_memory_contexts where level = 1;
type | name | ident | parent | level | ?column?
----------+------------------+-------+--------+-------+----------
- AllocSet | TopMemoryContext | | | 0 | t
+ AllocSet | TopMemoryContext | | | 1 | t
(1 row)
-- We can exercise some MemoryContext type stats functions. Most of the
@@ -51,6 +51,20 @@ from pg_backend_memory_contexts where name = 'Caller tuples';
(1 row)
rollback;
+-- Further sanity checks on pg_backend_memory_contexts. We expect
+-- CacheMemoryContext to have multiple children. Ensure that's the case.
+with contexts as (
+ select * from pg_backend_memory_contexts
+)
+select count(*) > 1
+from contexts c1, contexts c2
+where c2.name = 'CacheMemoryContext'
+and c1.path[c2.level] = c2.path[c2.level];
+ ?column?
+----------
+ t
+(1 row)
+
-- At introduction, pg_config had 23 entries; it may grow
select count(*) > 20 as ok from pg_config;
ok
diff --git a/src/test/regress/sql/sysviews.sql b/src/test/regress/sql/sysviews.sql
index 7edac2fde14..15e2a9e7417 100644
--- a/src/test/regress/sql/sysviews.sql
+++ b/src/test/regress/sql/sysviews.sql
@@ -15,7 +15,7 @@ select count(*) >= 0 as ok from pg_available_extensions;
-- The entire output of pg_backend_memory_contexts is not stable,
-- we test only the existence and basic condition of TopMemoryContext.
select type, name, ident, parent, level, total_bytes >= free_bytes
- from pg_backend_memory_contexts where level = 0;
+ from pg_backend_memory_contexts where level = 1;
-- We can exercise some MemoryContext type stats functions. Most of the
-- column values are too platform-dependant to display.
@@ -32,6 +32,16 @@ select type, name, parent, total_bytes > 0, total_nblocks, free_bytes > 0, free_
from pg_backend_memory_contexts where name = 'Caller tuples';
rollback;
+-- Further sanity checks on pg_backend_memory_contexts. We expect
+-- CacheMemoryContext to have multiple children. Ensure that's the case.
+with contexts as (
+ select * from pg_backend_memory_contexts
+)
+select count(*) > 1
+from contexts c1, contexts c2
+where c2.name = 'CacheMemoryContext'
+and c1.path[c2.level] = c2.path[c2.level];
+
-- At introduction, pg_config had 23 entries; it may grow
select count(*) > 20 as ok from pg_config;