Branch data 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 "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
48 : 526 : int_list_to_array(const List *list)
49 : : {
50 : 526 : Datum *datum_array;
51 : 526 : int length;
52 : 526 : ArrayType *result_array;
53 : :
54 : 526 : length = list_length(list);
55 : 526 : datum_array = palloc_array(Datum, length);
56 : :
57 [ + + + - : 2605 : foreach_int(i, list)
+ + + + ]
58 : 2079 : datum_array[foreach_current_index(i)] = Int32GetDatum(i);
59 : :
60 : 526 : result_array = construct_array_builtin(datum_array, length, INT4OID);
61 : :
62 : 1052 : return PointerGetDatum(result_array);
63 : 526 : }
64 : :
65 : : /*
66 : : * PutMemoryContextsStatsTupleStore
67 : : * Add details for the given MemoryContext to 'tupstore'.
68 : : */
69 : : static void
70 : 526 : 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 : 526 : Datum values[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
77 : 526 : bool nulls[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
78 : 526 : MemoryContextCounters stat;
79 : 526 : List *path = NIL;
80 : 526 : const char *name;
81 : 526 : const char *ident;
82 : 526 : const char *type;
83 : :
84 [ + - + + : 526 : Assert(MemoryContextIsValid(context));
+ - + + ]
85 : :
86 : : /*
87 : : * Figure out the transient context_id of this context and each of its
88 : : * ancestors.
89 : : */
90 [ + + ]: 2079 : for (MemoryContext cur = context; cur != NULL; cur = cur->parent)
91 : : {
92 : 1553 : MemoryContextId *entry;
93 : 1553 : bool found;
94 : :
95 : 1553 : entry = hash_search(context_id_lookup, &cur, HASH_FIND, &found);
96 : :
97 [ + - ]: 1553 : if (!found)
98 [ # # # # ]: 0 : elog(ERROR, "hash table corrupted");
99 : 1553 : path = lcons_int(entry->context_id, path);
100 : 1553 : }
101 : :
102 : : /* Examine the context itself */
103 : 526 : memset(&stat, 0, sizeof(stat));
104 : 526 : (*context->methods->stats) (context, NULL, NULL, &stat, true);
105 : :
106 : 526 : memset(values, 0, sizeof(values));
107 : 526 : memset(nulls, 0, sizeof(nulls));
108 : :
109 : 526 : name = context->name;
110 : 526 : 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 [ + + + + ]: 526 : if (ident && strcmp(name, "dynahash") == 0)
117 : : {
118 : 50 : name = ident;
119 : 50 : ident = NULL;
120 : 50 : }
121 : :
122 [ + - ]: 526 : if (name)
123 : 526 : values[0] = CStringGetTextDatum(name);
124 : : else
125 : 0 : nulls[0] = true;
126 : :
127 [ + + ]: 526 : if (ident)
128 : : {
129 : 379 : int idlen = strlen(ident);
130 : 379 : 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 : : */
136 [ + - ]: 379 : if (idlen >= MEMORY_CONTEXT_IDENT_DISPLAY_SIZE)
137 : 0 : idlen = pg_mbcliplen(ident, idlen, MEMORY_CONTEXT_IDENT_DISPLAY_SIZE - 1);
138 : :
139 : 379 : memcpy(clipped_ident, ident, idlen);
140 : 379 : clipped_ident[idlen] = '\0';
141 : 379 : values[1] = CStringGetTextDatum(clipped_ident);
142 : 379 : }
143 : : else
144 : 147 : nulls[1] = true;
145 : :
146 [ - + + - : 526 : switch (context->type)
+ ]
147 : : {
148 : : case T_AllocSetContext:
149 : 520 : type = "AllocSet";
150 : 520 : break;
151 : : case T_GenerationContext:
152 : 5 : type = "Generation";
153 : 5 : break;
154 : : case T_SlabContext:
155 : 0 : type = "Slab";
156 : 0 : break;
157 : : case T_BumpContext:
158 : 1 : type = "Bump";
159 : 1 : break;
160 : : default:
161 : 0 : type = "???";
162 : 0 : break;
163 : : }
164 : :
165 : 526 : values[2] = CStringGetTextDatum(type);
166 : 526 : values[3] = Int32GetDatum(list_length(path)); /* level */
167 : 526 : values[4] = int_list_to_array(path);
168 : 526 : values[5] = Int64GetDatum(stat.totalspace);
169 : 526 : values[6] = Int64GetDatum(stat.nblocks);
170 : 526 : values[7] = Int64GetDatum(stat.freespace);
171 : 526 : values[8] = Int64GetDatum(stat.freechunks);
172 : 526 : values[9] = Int64GetDatum(stat.totalspace - stat.freespace);
173 : :
174 : 526 : tuplestore_putvalues(tupstore, tupdesc, values, nulls);
175 : 526 : list_free(path);
176 : 526 : }
177 : :
178 : : /*
179 : : * pg_get_backend_memory_contexts
180 : : * SQL SRF showing backend memory context.
181 : : */
182 : : Datum
183 : 4 : pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
184 : : {
185 : 4 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
186 : 4 : int context_id;
187 : 4 : List *contexts;
188 : 4 : HASHCTL ctl;
189 : 4 : HTAB *context_id_lookup;
190 : :
191 : 4 : ctl.keysize = sizeof(MemoryContext);
192 : 4 : ctl.entrysize = sizeof(MemoryContextId);
193 : 4 : ctl.hcxt = CurrentMemoryContext;
194 : :
195 : 4 : context_id_lookup = hash_create("pg_get_backend_memory_contexts",
196 : : 256,
197 : : &ctl,
198 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
199 : :
200 : 4 : 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 : : */
212 : 4 : contexts = list_make1(TopMemoryContext);
213 : :
214 : : /* TopMemoryContext will always have a context_id of 1 */
215 : 4 : context_id = 1;
216 : :
217 [ + + + - : 534 : foreach_ptr(MemoryContextData, cur, contexts)
+ + + + ]
218 : : {
219 : 526 : MemoryContextId *entry;
220 : 526 : 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 : : */
227 : 526 : entry = (MemoryContextId *) hash_search(context_id_lookup, &cur,
228 : : HASH_ENTER, &found);
229 : 526 : entry->context_id = context_id++;
230 [ + - ]: 526 : Assert(!found);
231 : :
232 : 1052 : PutMemoryContextsStatsTupleStore(rsinfo->setResult,
233 : 526 : rsinfo->setDesc,
234 : 526 : cur,
235 : 526 : context_id_lookup);
236 : :
237 : : /*
238 : : * Append all children onto the contexts list so they're processed by
239 : : * subsequent iterations.
240 : : */
241 [ + + ]: 1048 : for (MemoryContext c = cur->firstchild; c != NULL; c = c->nextchild)
242 : 522 : contexts = lappend(contexts, c);
243 : 530 : }
244 : :
245 : 4 : hash_destroy(context_id_lookup);
246 : :
247 : 4 : return (Datum) 0;
248 : 4 : }
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
264 : 3 : pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
265 : : {
266 : 3 : int pid = PG_GETARG_INT32(0);
267 : 3 : PGPROC *proc;
268 : 3 : ProcNumber procNumber = INVALID_PROC_NUMBER;
269 : :
270 : : /*
271 : : * See if the process with given pid is a backend or an auxiliary process.
272 : : */
273 : 3 : proc = BackendPidGetProc(pid);
274 [ + + ]: 3 : if (proc == NULL)
275 : 1 : 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 : : */
286 [ + - ]: 3 : 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 : : */
292 [ # # # # ]: 0 : ereport(WARNING,
293 : : (errmsg("PID %d is not a PostgreSQL server process", pid)));
294 : 0 : PG_RETURN_BOOL(false);
295 : : }
296 : :
297 : 3 : procNumber = GetNumberFromPGProc(proc);
298 [ + - ]: 3 : if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, procNumber) < 0)
299 : : {
300 : : /* Again, just a warning to allow loops */
301 [ # # # # ]: 0 : ereport(WARNING,
302 : : (errmsg("could not send signal to process %d: %m", pid)));
303 : 0 : PG_RETURN_BOOL(false);
304 : : }
305 : :
306 : 3 : PG_RETURN_BOOL(true);
307 : 3 : }
|