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 @@ level int4 - 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. + + + + + + path int4[] + + + 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;