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