Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * mcxtfuncs.c
4 : : * Functions to show backend memory context.
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/utils/adt/mcxtfuncs.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : :
16 : : #include "postgres.h"
17 : :
18 : : #include "funcapi.h"
19 : : #include "mb/pg_wchar.h"
20 : : #include "storage/proc.h"
21 : : #include "storage/procarray.h"
22 : : #include "storage/procsignal.h"
23 : : #include "utils/array.h"
24 : : #include "utils/builtins.h"
25 : : #include "utils/hsearch.h"
26 : :
27 : : /* ----------
28 : : * The max bytes for showing identifiers of MemoryContext.
29 : : * ----------
30 : : */
31 : : #define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE 1024
32 : :
33 : : /*
34 : : * MemoryContextId
35 : : * Used for storage of transient identifiers for
36 : : * pg_get_backend_memory_contexts.
37 : : */
38 : : typedef struct MemoryContextId
39 : : {
40 : : MemoryContext context;
41 : : int context_id;
42 : : } MemoryContextId;
43 : :
44 : : /*
45 : : * int_list_to_array
46 : : * Convert an IntList to an array of INT4OIDs.
47 : : */
48 : : static Datum
598 drowley@postgresql.o 49 :CBC 1563 : int_list_to_array(const List *list)
50 : : {
51 : : Datum *datum_array;
52 : : int length;
53 : : ArrayType *result_array;
54 : :
55 : 1563 : length = list_length(list);
95 michael@paquier.xyz 56 :GNC 1563 : datum_array = palloc_array(Datum, length);
57 : :
598 drowley@postgresql.o 58 [ + - + + :CBC 7755 : foreach_int(i, list)
+ + ]
59 : 4629 : datum_array[foreach_current_index(i)] = Int32GetDatum(i);
60 : :
61 : 1563 : result_array = construct_array_builtin(datum_array, length, INT4OID);
62 : :
63 : 1563 : return PointerGetDatum(result_array);
64 : : }
65 : :
66 : : /*
67 : : * PutMemoryContextsStatsTupleStore
68 : : * Add details for the given MemoryContext to 'tupstore'.
69 : : */
70 : : static void
2027 fujii@postgresql.org 71 : 1563 : PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
72 : : TupleDesc tupdesc, MemoryContext context,
73 : : HTAB *context_id_lookup)
74 : : {
75 : : #define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS 10
76 : :
77 : : Datum values[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
78 : : bool nulls[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
79 : : MemoryContextCounters stat;
598 drowley@postgresql.o 80 : 1563 : List *path = NIL;
81 : : const char *name;
82 : : const char *ident;
83 : : const char *type;
84 : :
1234 peter@eisentraut.org 85 [ + - + + : 1563 : Assert(MemoryContextIsValid(context));
+ - + + -
+ ]
86 : :
87 : : /*
88 : : * Figure out the transient context_id of this context and each of its
89 : : * ancestors.
90 : : */
598 drowley@postgresql.o 91 [ + + ]: 6192 : for (MemoryContext cur = context; cur != NULL; cur = cur->parent)
92 : : {
93 : : MemoryContextId *entry;
94 : : bool found;
95 : :
96 : 4629 : entry = hash_search(context_id_lookup, &cur, HASH_FIND, &found);
97 : :
98 [ - + ]: 4629 : if (!found)
598 drowley@postgresql.o 99 [ # # ]:UBC 0 : elog(ERROR, "hash table corrupted");
598 drowley@postgresql.o 100 :CBC 4629 : path = lcons_int(entry->context_id, path);
101 : : }
102 : :
103 : : /* Examine the context itself */
2027 fujii@postgresql.org 104 : 1563 : memset(&stat, 0, sizeof(stat));
598 drowley@postgresql.o 105 : 1563 : (*context->methods->stats) (context, NULL, NULL, &stat, true);
106 : :
2027 fujii@postgresql.org 107 : 1563 : memset(values, 0, sizeof(values));
108 : 1563 : memset(nulls, 0, sizeof(nulls));
109 : :
580 drowley@postgresql.o 110 : 1563 : name = context->name;
111 : 1563 : ident = context->ident;
112 : :
113 : : /*
114 : : * To be consistent with logging output, we label dynahash contexts with
115 : : * just the hash table name as with MemoryContextStatsPrint().
116 : : */
117 [ + + + + ]: 1563 : if (ident && strcmp(name, "dynahash") == 0)
118 : : {
119 : 138 : name = ident;
120 : 138 : ident = NULL;
121 : : }
122 : :
2027 fujii@postgresql.org 123 [ + - ]: 1563 : if (name)
124 : 1563 : values[0] = CStringGetTextDatum(name);
125 : : else
2027 fujii@postgresql.org 126 :UBC 0 : nulls[0] = true;
127 : :
2027 fujii@postgresql.org 128 [ + + ]:CBC 1563 : if (ident)
129 : : {
1768 tgl@sss.pgh.pa.us 130 : 1137 : int idlen = strlen(ident);
131 : : char clipped_ident[MEMORY_CONTEXT_IDENT_DISPLAY_SIZE];
132 : :
133 : : /*
134 : : * Some identifiers such as SQL query string can be very long,
135 : : * truncate oversize identifiers.
136 : : */
2027 fujii@postgresql.org 137 [ - + ]: 1137 : if (idlen >= MEMORY_CONTEXT_IDENT_DISPLAY_SIZE)
2027 fujii@postgresql.org 138 :UBC 0 : idlen = pg_mbcliplen(ident, idlen, MEMORY_CONTEXT_IDENT_DISPLAY_SIZE - 1);
139 : :
2027 fujii@postgresql.org 140 :CBC 1137 : memcpy(clipped_ident, ident, idlen);
141 : 1137 : clipped_ident[idlen] = '\0';
142 : 1137 : values[1] = CStringGetTextDatum(clipped_ident);
143 : : }
144 : : else
145 : 426 : nulls[1] = true;
146 : :
296 dgustafsson@postgres 147 [ + + - + : 1563 : switch (context->type)
- ]
148 : : {
341 149 : 1545 : case T_AllocSetContext:
296 150 : 1545 : type = "AllocSet";
341 151 : 1545 : break;
152 : 15 : case T_GenerationContext:
296 153 : 15 : type = "Generation";
341 154 : 15 : break;
341 dgustafsson@postgres 155 :UBC 0 : case T_SlabContext:
296 156 : 0 : type = "Slab";
341 157 : 0 : break;
341 dgustafsson@postgres 158 :CBC 3 : case T_BumpContext:
296 159 : 3 : type = "Bump";
341 160 : 3 : break;
341 dgustafsson@postgres 161 :UBC 0 : default:
296 162 : 0 : type = "???";
341 163 : 0 : break;
164 : : }
165 : :
296 dgustafsson@postgres 166 :CBC 1563 : values[2] = CStringGetTextDatum(type);
167 : 1563 : values[3] = Int32GetDatum(list_length(path)); /* level */
168 : 1563 : values[4] = int_list_to_array(path);
169 : 1563 : values[5] = Int64GetDatum(stat.totalspace);
170 : 1563 : values[6] = Int64GetDatum(stat.nblocks);
171 : 1563 : values[7] = Int64GetDatum(stat.freespace);
172 : 1563 : values[8] = Int64GetDatum(stat.freechunks);
173 : 1563 : values[9] = Int64GetDatum(stat.totalspace - stat.freespace);
174 : :
175 : 1563 : tuplestore_putvalues(tupstore, tupdesc, values, nulls);
176 : 1563 : list_free(path);
341 177 : 1563 : }
178 : :
179 : : /*
180 : : * pg_get_backend_memory_contexts
181 : : * SQL SRF showing backend memory context.
182 : : */
183 : : Datum
2027 fujii@postgresql.org 184 : 12 : pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
185 : : {
186 : 12 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
187 : : int context_id;
188 : : List *contexts;
189 : : HASHCTL ctl;
190 : : HTAB *context_id_lookup;
191 : :
598 drowley@postgresql.o 192 : 12 : ctl.keysize = sizeof(MemoryContext);
296 dgustafsson@postgres 193 : 12 : ctl.entrysize = sizeof(MemoryContextId);
598 drowley@postgresql.o 194 : 12 : ctl.hcxt = CurrentMemoryContext;
195 : :
196 : 12 : context_id_lookup = hash_create("pg_get_backend_memory_contexts",
197 : : 256,
198 : : &ctl,
199 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
200 : :
1244 michael@paquier.xyz 201 : 12 : InitMaterializedSRF(fcinfo, 0);
202 : :
203 : : /*
204 : : * Here we use a non-recursive algorithm to visit all MemoryContexts
205 : : * starting with TopMemoryContext. The reason we avoid using a recursive
206 : : * algorithm is because we want to assign the context_id breadth-first.
207 : : * I.e. all contexts at level 1 are assigned IDs before contexts at level
208 : : * 2. Because contexts closer to TopMemoryContext are less likely to
209 : : * change, this makes the assigned context_id more stable. Otherwise, if
210 : : * the first child of TopMemoryContext obtained an additional grandchild,
211 : : * the context_id for the second child of TopMemoryContext would change.
212 : : */
598 drowley@postgresql.o 213 : 12 : contexts = list_make1(TopMemoryContext);
214 : :
215 : : /* TopMemoryContext will always have a context_id of 1 */
216 : 12 : context_id = 1;
217 : :
218 [ + - + + : 1587 : foreach_ptr(MemoryContextData, cur, contexts)
+ + ]
219 : : {
220 : : MemoryContextId *entry;
221 : : bool found;
222 : :
223 : : /*
224 : : * Record the context_id that we've assigned to each MemoryContext.
225 : : * PutMemoryContextsStatsTupleStore needs this to populate the "path"
226 : : * column with the parent context_ids.
227 : : */
296 dgustafsson@postgres 228 : 1563 : entry = (MemoryContextId *) hash_search(context_id_lookup, &cur,
229 : : HASH_ENTER, &found);
598 drowley@postgresql.o 230 : 1563 : entry->context_id = context_id++;
231 [ - + ]: 1563 : Assert(!found);
232 : :
233 : 1563 : PutMemoryContextsStatsTupleStore(rsinfo->setResult,
234 : : rsinfo->setDesc,
235 : : cur,
236 : : context_id_lookup);
237 : :
238 : : /*
239 : : * Append all children onto the contexts list so they're processed by
240 : : * subsequent iterations.
241 : : */
242 [ + + ]: 3114 : for (MemoryContext c = cur->firstchild; c != NULL; c = c->nextchild)
243 : 1551 : contexts = lappend(contexts, c);
244 : : }
245 : :
246 : 12 : hash_destroy(context_id_lookup);
247 : :
2027 fujii@postgresql.org 248 : 12 : return (Datum) 0;
249 : : }
250 : :
251 : : /*
252 : : * pg_log_backend_memory_contexts
253 : : * Signal a backend or an auxiliary process to log its memory contexts.
254 : : *
255 : : * By default, only superusers are allowed to signal to log the memory
256 : : * contexts because allowing any users to issue this request at an unbounded
257 : : * rate would cause lots of log messages and which can lead to denial of
258 : : * service. Additional roles can be permitted with GRANT.
259 : : *
260 : : * On receipt of this signal, a backend or an auxiliary process sets the flag
261 : : * in the signal handler, which causes the next CHECK_FOR_INTERRUPTS()
262 : : * or process-specific interrupt handler to log the memory contexts.
263 : : */
264 : : Datum
1804 265 : 9 : pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
266 : : {
267 : 9 : int pid = PG_GETARG_INT32(0);
268 : : PGPROC *proc;
742 heikki.linnakangas@i 269 : 9 : ProcNumber procNumber = INVALID_PROC_NUMBER;
270 : :
271 : : /*
272 : : * See if the process with given pid is a backend or an auxiliary process.
273 : : */
274 : 9 : proc = BackendPidGetProc(pid);
275 [ + + ]: 9 : if (proc == NULL)
1524 fujii@postgresql.org 276 : 3 : proc = AuxiliaryPidGetProc(pid);
277 : :
278 : : /*
279 : : * BackendPidGetProc() and AuxiliaryPidGetProc() return NULL if the pid
280 : : * isn't valid; but by the time we reach kill(), a process for which we
281 : : * get a valid proc here might have terminated on its own. There's no way
282 : : * to acquire a lock on an arbitrary process to prevent that. But since
283 : : * this mechanism is usually used to debug a backend or an auxiliary
284 : : * process running and consuming lots of memory, that it might end on its
285 : : * own first and its memory contexts are not logged is not a problem.
286 : : */
1804 287 [ - + ]: 9 : if (proc == NULL)
288 : : {
289 : : /*
290 : : * This is just a warning so a loop-through-resultset will not abort
291 : : * if one backend terminated on its own during the run.
292 : : */
1804 fujii@postgresql.org 293 [ # # ]:UBC 0 : ereport(WARNING,
294 : : (errmsg("PID %d is not a PostgreSQL server process", pid)));
295 : 0 : PG_RETURN_BOOL(false);
296 : : }
297 : :
742 heikki.linnakangas@i 298 :CBC 9 : procNumber = GetNumberFromPGProc(proc);
299 [ - + ]: 9 : if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, procNumber) < 0)
300 : : {
301 : : /* Again, just a warning to allow loops */
1804 fujii@postgresql.org 302 [ # # ]:UBC 0 : ereport(WARNING,
303 : : (errmsg("could not send signal to process %d: %m", pid)));
304 : 0 : PG_RETURN_BOOL(false);
305 : : }
306 : :
1804 fujii@postgresql.org 307 :CBC 9 : PG_RETURN_BOOL(true);
308 : : }
|