aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/mcxtfuncs.c
diff options
context:
space:
mode:
authorDavid Rowley <drowley@postgresql.org>2024-07-25 15:03:28 +1200
committerDavid Rowley <drowley@postgresql.org>2024-07-25 15:03:28 +1200
commit32d3ed8165f821f6994c95230a9a4b2ff0ce9f12 (patch)
treee0372387e67360550b55183841950833cfe478c9 /src/backend/utils/adt/mcxtfuncs.c
parent64c39bd5047e6ee045bbc80ea1399feb59cd2b53 (diff)
downloadpostgresql-32d3ed8165f821f6994c95230a9a4b2ff0ce9f12.tar.gz
postgresql-32d3ed8165f821f6994c95230a9a4b2ff0ce9f12.zip
Add path column to pg_backend_memory_contexts view
"path" provides a reliable method of determining the parent/child relationships between memory contexts. Previously this could be done in a non-reliable way by writing a recursive query and joining the "parent" and "name" columns. This wasn't reliable as the names were not unique, which could result in joining to the wrong parent. To make this reliable, "path" stores an array of numerical identifiers starting with the identifier for TopLevelMemoryContext. It contains an element for each intermediate parent between that and the current context. Incompatibility: Here we also adjust the "level" column to make it 1-based rather than 0-based. A 1-based level provides a convenient way to access elements in the "path" array. e.g. path[level] gives the identifier for the current context. Identifiers are not stable across multiple evaluations of the view. In an attempt to make these more stable for ad-hoc queries, the identifiers are assigned breadth-first. Contexts closer to TopLevelMemoryContext are less likely to change between queries and during queries. Author: Melih Mutlu <m.melihmutlu@gmail.com> Discussion: https://postgr.es/m/CAGPVpCThLyOsj3e_gYEvLoHkr5w=tadDiN_=z2OwsK3VJppeBA@mail.gmail.com Reviewed-by: Andres Freund, Stephen Frost, Atsushi Torikoshi, Reviewed-by: Michael Paquier, Robert Haas, David Rowley
Diffstat (limited to 'src/backend/utils/adt/mcxtfuncs.c')
-rw-r--r--src/backend/utils/adt/mcxtfuncs.c180
1 files changed, 151 insertions, 29 deletions
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.
@@ -28,46 +30,106 @@
#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;
}