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
49 1433 : int_list_to_array(const List *list)
50 : {
51 : Datum *datum_array;
52 : int length;
53 : ArrayType *result_array;
54 :
55 1433 : length = list_length(list);
56 1433 : datum_array = palloc_array(Datum, length);
57 :
58 7093 : foreach_int(i, list)
59 4227 : datum_array[foreach_current_index(i)] = Int32GetDatum(i);
60 :
61 1433 : result_array = construct_array_builtin(datum_array, length, INT4OID);
62 :
63 1433 : return PointerGetDatum(result_array);
64 : }
65 :
66 : /*
67 : * PutMemoryContextsStatsTupleStore
68 : * Add details for the given MemoryContext to 'tupstore'.
69 : */
70 : static void
71 1433 : 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;
80 1433 : List *path = NIL;
81 : const char *name;
82 : const char *ident;
83 : const char *type;
84 :
85 : Assert(MemoryContextIsValid(context));
86 :
87 : /*
88 : * Figure out the transient context_id of this context and each of its
89 : * ancestors.
90 : */
91 5660 : for (MemoryContext cur = context; cur != NULL; cur = cur->parent)
92 : {
93 : MemoryContextId *entry;
94 : bool found;
95 :
96 4227 : entry = hash_search(context_id_lookup, &cur, HASH_FIND, &found);
97 :
98 4227 : if (!found)
99 0 : elog(ERROR, "hash table corrupted");
100 4227 : path = lcons_int(entry->context_id, path);
101 : }
102 :
103 : /* Examine the context itself */
104 1433 : memset(&stat, 0, sizeof(stat));
105 1433 : (*context->methods->stats) (context, NULL, NULL, &stat, true);
106 :
107 1433 : memset(values, 0, sizeof(values));
108 1433 : memset(nulls, 0, sizeof(nulls));
109 :
110 1433 : name = context->name;
111 1433 : 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 1433 : if (ident && strcmp(name, "dynahash") == 0)
118 : {
119 150 : name = ident;
120 150 : ident = NULL;
121 : }
122 :
123 1433 : if (name)
124 1433 : values[0] = CStringGetTextDatum(name);
125 : else
126 0 : nulls[0] = true;
127 :
128 1433 : if (ident)
129 : {
130 995 : 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 : */
137 995 : if (idlen >= MEMORY_CONTEXT_IDENT_DISPLAY_SIZE)
138 0 : idlen = pg_mbcliplen(ident, idlen, MEMORY_CONTEXT_IDENT_DISPLAY_SIZE - 1);
139 :
140 995 : memcpy(clipped_ident, ident, idlen);
141 995 : clipped_ident[idlen] = '\0';
142 995 : values[1] = CStringGetTextDatum(clipped_ident);
143 : }
144 : else
145 438 : nulls[1] = true;
146 :
147 1433 : switch (context->type)
148 : {
149 1415 : case T_AllocSetContext:
150 1415 : type = "AllocSet";
151 1415 : break;
152 15 : case T_GenerationContext:
153 15 : type = "Generation";
154 15 : break;
155 0 : case T_SlabContext:
156 0 : type = "Slab";
157 0 : break;
158 3 : case T_BumpContext:
159 3 : type = "Bump";
160 3 : break;
161 0 : default:
162 0 : type = "???";
163 0 : break;
164 : }
165 :
166 1433 : values[2] = CStringGetTextDatum(type);
167 1433 : values[3] = Int32GetDatum(list_length(path)); /* level */
168 1433 : values[4] = int_list_to_array(path);
169 1433 : values[5] = Int64GetDatum(stat.totalspace);
170 1433 : values[6] = Int64GetDatum(stat.nblocks);
171 1433 : values[7] = Int64GetDatum(stat.freespace);
172 1433 : values[8] = Int64GetDatum(stat.freechunks);
173 1433 : values[9] = Int64GetDatum(stat.totalspace - stat.freespace);
174 :
175 1433 : tuplestore_putvalues(tupstore, tupdesc, values, nulls);
176 1433 : list_free(path);
177 1433 : }
178 :
179 : /*
180 : * pg_get_backend_memory_contexts
181 : * SQL SRF showing backend memory context.
182 : */
183 : Datum
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 :
192 12 : ctl.keysize = sizeof(MemoryContext);
193 12 : ctl.entrysize = sizeof(MemoryContextId);
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 :
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 : */
213 12 : contexts = list_make1(TopMemoryContext);
214 :
215 : /* TopMemoryContext will always have a context_id of 1 */
216 12 : context_id = 1;
217 :
218 1457 : 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 : */
228 1433 : entry = (MemoryContextId *) hash_search(context_id_lookup, &cur,
229 : HASH_ENTER, &found);
230 1433 : entry->context_id = context_id++;
231 : Assert(!found);
232 :
233 1433 : 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 2854 : for (MemoryContext c = cur->firstchild; c != NULL; c = c->nextchild)
243 1421 : contexts = lappend(contexts, c);
244 : }
245 :
246 12 : hash_destroy(context_id_lookup);
247 :
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
265 9 : pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
266 : {
267 9 : int pid = PG_GETARG_INT32(0);
268 : PGPROC *proc;
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)
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 : */
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 : */
293 0 : ereport(WARNING,
294 : (errmsg("PID %d is not a PostgreSQL server process", pid)));
295 0 : PG_RETURN_BOOL(false);
296 : }
297 :
298 9 : procNumber = GetNumberFromPGProc(proc);
299 9 : if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, procNumber) < 0)
300 : {
301 : /* Again, just a warning to allow loops */
302 0 : ereport(WARNING,
303 : (errmsg("could not send signal to process %d: %m", pid)));
304 0 : PG_RETURN_BOOL(false);
305 : }
306 :
307 9 : PG_RETURN_BOOL(true);
308 : }
|