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