Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 32d3ed8

Browse files
committed
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
1 parent 64c39bd commit 32d3ed8

File tree

8 files changed

+225
-40
lines changed

8 files changed

+225
-40
lines changed

doc/src/sgml/system-views.sgml

+39-1
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,22 @@
504504
<structfield>level</structfield> <type>int4</type>
505505
</para>
506506
<para>
507-
Distance from TopMemoryContext in context tree
507+
The 1-based level of the context in the memory context hierarchy. The
508+
level of a context also shows the position of that context in the
509+
<structfield>path</structfield> column.
510+
</para></entry>
511+
</row>
512+
513+
<row>
514+
<entry role="catalog_table_entry"><para role="column_definition">
515+
<structfield>path</structfield> <type>int4[]</type>
516+
</para>
517+
<para>
518+
Array of transient numerical identifiers to describe the memory
519+
context hierarchy. The first element is for
520+
<literal>TopMemoryContext</literal>, subsequent elements contain
521+
intermediate parents and the final element contains the identifier for
522+
the current context.
508523
</para></entry>
509524
</row>
510525

@@ -561,6 +576,29 @@
561576
read only by superusers or roles with the privileges of the
562577
<literal>pg_read_all_stats</literal> role.
563578
</para>
579+
580+
<para>
581+
Since memory contexts are created and destroyed during the running of a
582+
query, the identifiers stored in the <structfield>path</structfield> column
583+
can be unstable between multiple invocations of the view in the same query.
584+
The example below demonstrates an effective usage of this column and
585+
calculates the total number of bytes used by
586+
<literal>CacheMemoryContext</literal> and all of its children:
587+
588+
<programlisting>
589+
WITH memory_contexts AS (
590+
SELECT * FROM pg_backend_memory_contexts
591+
)
592+
SELECT sum(c1.total_bytes)
593+
FROM memory_contexts c1, memory_contexts c2
594+
WHERE c2.name = 'CacheMemoryContext'
595+
AND c1.path[c2.level] = c2.path[c2.level];
596+
</programlisting>
597+
598+
The <link linkend="queries-with">Common Table Expression</link> is used
599+
to ensure the context IDs in the <structfield>path</structfield> column
600+
match between both evaluations of the view.
601+
</para>
564602
</sect1>
565603

566604
<sect1 id="view-pg-config">

src/backend/utils/adt/mcxtfuncs.c

+151-29
Original file line numberDiff line numberDiff line change
@@ -19,55 +19,117 @@
1919
#include "mb/pg_wchar.h"
2020
#include "storage/proc.h"
2121
#include "storage/procarray.h"
22+
#include "utils/array.h"
2223
#include "utils/builtins.h"
24+
#include "utils/hsearch.h"
2325

2426
/* ----------
2527
* The max bytes for showing identifiers of MemoryContext.
2628
* ----------
2729
*/
2830
#define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE 1024
2931

32+
/*
33+
* MemoryContextId
34+
* Used for storage of transient identifiers for
35+
* pg_get_backend_memory_contexts.
36+
*/
37+
typedef struct MemoryContextId
38+
{
39+
MemoryContext context;
40+
int context_id;
41+
} MemoryContextId;
42+
43+
/*
44+
* get_memory_context_name_and_ident
45+
* Populate *name and *ident from the name and ident from 'context'.
46+
*/
47+
static void
48+
get_memory_context_name_and_ident(MemoryContext context, const char **const name,
49+
const char **const ident)
50+
{
51+
*name = context->name;
52+
*ident = context->ident;
53+
54+
/*
55+
* To be consistent with logging output, we label dynahash contexts with
56+
* just the hash table name as with MemoryContextStatsPrint().
57+
*/
58+
if (ident && strcmp(*name, "dynahash") == 0)
59+
{
60+
*name = *ident;
61+
*ident = NULL;
62+
}
63+
}
64+
65+
/*
66+
* int_list_to_array
67+
* Convert an IntList to an array of INT4OIDs.
68+
*/
69+
static Datum
70+
int_list_to_array(const List *list)
71+
{
72+
Datum *datum_array;
73+
int length;
74+
ArrayType *result_array;
75+
76+
length = list_length(list);
77+
datum_array = (Datum *) palloc(length * sizeof(Datum));
78+
79+
foreach_int(i, list)
80+
datum_array[foreach_current_index(i)] = Int32GetDatum(i);
81+
82+
result_array = construct_array_builtin(datum_array, length, INT4OID);
83+
84+
return PointerGetDatum(result_array);
85+
}
86+
3087
/*
3188
* PutMemoryContextsStatsTupleStore
32-
* One recursion level for pg_get_backend_memory_contexts.
89+
* Add details for the given MemoryContext to 'tupstore'.
3390
*/
3491
static void
3592
PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
3693
TupleDesc tupdesc, MemoryContext context,
37-
const char *parent, int level)
94+
HTAB *context_id_lookup)
3895
{
39-
#define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS 10
96+
#define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS 11
4097

4198
Datum values[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
4299
bool nulls[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
43100
MemoryContextCounters stat;
44-
MemoryContext child;
101+
List *path = NIL;
45102
const char *name;
46103
const char *ident;
47104
const char *type;
48105

49106
Assert(MemoryContextIsValid(context));
50107

51-
name = context->name;
52-
ident = context->ident;
53-
54108
/*
55-
* To be consistent with logging output, we label dynahash contexts with
56-
* just the hash table name as with MemoryContextStatsPrint().
109+
* Figure out the transient context_id of this context and each of its
110+
* ancestors.
57111
*/
58-
if (ident && strcmp(name, "dynahash") == 0)
112+
for (MemoryContext cur = context; cur != NULL; cur = cur->parent)
59113
{
60-
name = ident;
61-
ident = NULL;
114+
MemoryContextId *entry;
115+
bool found;
116+
117+
entry = hash_search(context_id_lookup, &cur, HASH_FIND, &found);
118+
119+
if (!found)
120+
elog(ERROR, "hash table corrupted");
121+
path = lcons_int(entry->context_id, path);
62122
}
63123

64124
/* Examine the context itself */
65125
memset(&stat, 0, sizeof(stat));
66-
(*context->methods->stats) (context, NULL, (void *) &level, &stat, true);
126+
(*context->methods->stats) (context, NULL, NULL, &stat, true);
67127

68128
memset(values, 0, sizeof(values));
69129
memset(nulls, 0, sizeof(nulls));
70130

131+
get_memory_context_name_and_ident(context, &name, &ident);
132+
71133
if (name)
72134
values[0] = CStringGetTextDatum(name);
73135
else
@@ -92,8 +154,15 @@ PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
92154
else
93155
nulls[1] = true;
94156

95-
if (parent)
96-
values[2] = CStringGetTextDatum(parent);
157+
if (context->parent)
158+
{
159+
const char *parent_name,
160+
*parent_ident;
161+
162+
get_memory_context_name_and_ident(context->parent, &parent_name,
163+
&parent_ident);
164+
values[2] = CStringGetTextDatum(parent_name);
165+
}
97166
else
98167
nulls[2] = true;
99168

@@ -117,19 +186,16 @@ PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
117186
}
118187

119188
values[3] = CStringGetTextDatum(type);
120-
values[4] = Int32GetDatum(level);
121-
values[5] = Int64GetDatum(stat.totalspace);
122-
values[6] = Int64GetDatum(stat.nblocks);
123-
values[7] = Int64GetDatum(stat.freespace);
124-
values[8] = Int64GetDatum(stat.freechunks);
125-
values[9] = Int64GetDatum(stat.totalspace - stat.freespace);
126-
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
189+
values[4] = Int32GetDatum(list_length(path)); /* level */
190+
values[5] = int_list_to_array(path);
191+
values[6] = Int64GetDatum(stat.totalspace);
192+
values[7] = Int64GetDatum(stat.nblocks);
193+
values[8] = Int64GetDatum(stat.freespace);
194+
values[9] = Int64GetDatum(stat.freechunks);
195+
values[10] = Int64GetDatum(stat.totalspace - stat.freespace);
127196

128-
for (child = context->firstchild; child != NULL; child = child->nextchild)
129-
{
130-
PutMemoryContextsStatsTupleStore(tupstore, tupdesc,
131-
child, name, level + 1);
132-
}
197+
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
198+
list_free(path);
133199
}
134200

135201
/*
@@ -140,10 +206,66 @@ Datum
140206
pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
141207
{
142208
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
209+
int context_id;
210+
List *contexts;
211+
HASHCTL ctl;
212+
HTAB *context_id_lookup;
213+
214+
ctl.keysize = sizeof(MemoryContext);
215+
ctl.entrysize = sizeof(MemoryContextId);
216+
ctl.hcxt = CurrentMemoryContext;
217+
218+
context_id_lookup = hash_create("pg_get_backend_memory_contexts",
219+
256,
220+
&ctl,
221+
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
143222

144223
InitMaterializedSRF(fcinfo, 0);
145-
PutMemoryContextsStatsTupleStore(rsinfo->setResult, rsinfo->setDesc,
146-
TopMemoryContext, NULL, 0);
224+
225+
/*
226+
* Here we use a non-recursive algorithm to visit all MemoryContexts
227+
* starting with TopMemoryContext. The reason we avoid using a recursive
228+
* algorithm is because we want to assign the context_id breadth-first.
229+
* I.e. all contexts at level 1 are assigned IDs before contexts at level
230+
* 2. Because contexts closer to TopMemoryContext are less likely to
231+
* change, this makes the assigned context_id more stable. Otherwise, if
232+
* the first child of TopMemoryContext obtained an additional grandchild,
233+
* the context_id for the second child of TopMemoryContext would change.
234+
*/
235+
contexts = list_make1(TopMemoryContext);
236+
237+
/* TopMemoryContext will always have a context_id of 1 */
238+
context_id = 1;
239+
240+
foreach_ptr(MemoryContextData, cur, contexts)
241+
{
242+
MemoryContextId *entry;
243+
bool found;
244+
245+
/*
246+
* Record the context_id that we've assigned to each MemoryContext.
247+
* PutMemoryContextsStatsTupleStore needs this to populate the "path"
248+
* column with the parent context_ids.
249+
*/
250+
entry = (MemoryContextId *) hash_search(context_id_lookup, &cur,
251+
HASH_ENTER, &found);
252+
entry->context_id = context_id++;
253+
Assert(!found);
254+
255+
PutMemoryContextsStatsTupleStore(rsinfo->setResult,
256+
rsinfo->setDesc,
257+
cur,
258+
context_id_lookup);
259+
260+
/*
261+
* Append all children onto the contexts list so they're processed by
262+
* subsequent iterations.
263+
*/
264+
for (MemoryContext c = cur->firstchild; c != NULL; c = c->nextchild)
265+
contexts = lappend(contexts, c);
266+
}
267+
268+
hash_destroy(context_id_lookup);
147269

148270
return (Datum) 0;
149271
}

src/include/catalog/catversion.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@
5757
*/
5858

5959
/* yyyymmddN */
60-
#define CATALOG_VERSION_NO 202407111
60+
#define CATALOG_VERSION_NO 202407251
6161

6262
#endif

src/include/catalog/pg_proc.dat

+3-3
Original file line numberDiff line numberDiff line change
@@ -8290,9 +8290,9 @@
82908290
proname => 'pg_get_backend_memory_contexts', prorows => '100',
82918291
proretset => 't', provolatile => 'v', proparallel => 'r',
82928292
prorettype => 'record', proargtypes => '',
8293-
proallargtypes => '{text,text,text,text,int4,int8,int8,int8,int8,int8}',
8294-
proargmodes => '{o,o,o,o,o,o,o,o,o,o}',
8295-
proargnames => '{name, ident, parent, type, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes}',
8293+
proallargtypes => '{text,text,text,text,int4,_int4,int8,int8,int8,int8,int8}',
8294+
proargmodes => '{o,o,o,o,o,o,o,o,o,o,o}',
8295+
proargnames => '{name, ident, parent, type, level, path, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes}',
82968296
prosrc => 'pg_get_backend_memory_contexts' },
82978297

82988298
# logging memory contexts of the specified backend

src/include/nodes/memnodes.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ typedef struct MemoryContextData
128128
MemoryContext firstchild; /* head of linked list of children */
129129
MemoryContext prevchild; /* previous child of same parent */
130130
MemoryContext nextchild; /* next child of same parent */
131-
const char *name; /* context name (just for debugging) */
132-
const char *ident; /* context ID if any (just for debugging) */
131+
const char *name; /* context name */
132+
const char *ident; /* context ID if any */
133133
MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */
134134
} MemoryContextData;
135135

src/test/regress/expected/rules.out

+2-1
Original file line numberDiff line numberDiff line change
@@ -1308,12 +1308,13 @@ pg_backend_memory_contexts| SELECT name,
13081308
parent,
13091309
type,
13101310
level,
1311+
path,
13111312
total_bytes,
13121313
total_nblocks,
13131314
free_bytes,
13141315
free_chunks,
13151316
used_bytes
1316-
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);
1317+
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);
13171318
pg_config| SELECT name,
13181319
setting
13191320
FROM pg_config() pg_config(name, setting);

src/test/regress/expected/sysviews.out

+16-2
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ select count(*) >= 0 as ok from pg_available_extensions;
2222
-- The entire output of pg_backend_memory_contexts is not stable,
2323
-- we test only the existence and basic condition of TopMemoryContext.
2424
select type, name, ident, parent, level, total_bytes >= free_bytes
25-
from pg_backend_memory_contexts where level = 0;
25+
from pg_backend_memory_contexts where level = 1;
2626
type | name | ident | parent | level | ?column?
2727
----------+------------------+-------+--------+-------+----------
28-
AllocSet | TopMemoryContext | | | 0 | t
28+
AllocSet | TopMemoryContext | | | 1 | t
2929
(1 row)
3030

3131
-- We can exercise some MemoryContext type stats functions. Most of the
@@ -51,6 +51,20 @@ from pg_backend_memory_contexts where name = 'Caller tuples';
5151
(1 row)
5252

5353
rollback;
54+
-- Further sanity checks on pg_backend_memory_contexts. We expect
55+
-- CacheMemoryContext to have multiple children. Ensure that's the case.
56+
with contexts as (
57+
select * from pg_backend_memory_contexts
58+
)
59+
select count(*) > 1
60+
from contexts c1, contexts c2
61+
where c2.name = 'CacheMemoryContext'
62+
and c1.path[c2.level] = c2.path[c2.level];
63+
?column?
64+
----------
65+
t
66+
(1 row)
67+
5468
-- At introduction, pg_config had 23 entries; it may grow
5569
select count(*) > 20 as ok from pg_config;
5670
ok

0 commit comments

Comments
 (0)