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
51 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);
58 2216 : datum_array = palloc_array(Datum, length);
59 :
60 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
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;
82 2216 : List *path = NIL;
83 : const char *name;
84 : const char *ident;
85 : const char *type;
86 :
87 : Assert(MemoryContextIsValid(context));
88 :
89 : /*
90 : * Figure out the transient context_id of this context and each of its
91 : * ancestors.
92 : */
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)
101 0 : elog(ERROR, "hash table corrupted");
102 6564 : path = lcons_int(entry->context_id, path);
103 : }
104 :
105 : /* Examine the context itself */
106 2216 : memset(&stat, 0, sizeof(stat));
107 2216 : (*context->methods->stats) (context, NULL, NULL, &stat, true);
108 :
109 2216 : memset(values, 0, sizeof(values));
110 2216 : memset(nulls, 0, sizeof(nulls));
111 :
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 :
125 2216 : if (name)
126 2216 : values[0] = CStringGetTextDatum(name);
127 : else
128 0 : nulls[0] = true;
129 :
130 2216 : if (ident)
131 : {
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 : */
139 1644 : if (idlen >= MEMORY_CONTEXT_IDENT_DISPLAY_SIZE)
140 0 : idlen = pg_mbcliplen(ident, idlen, MEMORY_CONTEXT_IDENT_DISPLAY_SIZE - 1);
141 :
142 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 :
149 2216 : switch (context->type)
150 : {
151 2192 : case T_AllocSetContext:
152 2192 : type = "AllocSet";
153 2192 : break;
154 20 : case T_GenerationContext:
155 20 : type = "Generation";
156 20 : break;
157 0 : case T_SlabContext:
158 0 : type = "Slab";
159 0 : break;
160 4 : case T_BumpContext:
161 4 : type = "Bump";
162 4 : break;
163 0 : default:
164 0 : type = "???";
165 0 : break;
166 : }
167 :
168 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);
179 2216 : }
180 :
181 : /*
182 : * pg_get_backend_memory_contexts
183 : * SQL SRF showing backend memory context.
184 : */
185 : Datum
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 :
194 16 : ctl.keysize = sizeof(MemoryContext);
195 16 : ctl.entrysize = sizeof(MemoryContextId);
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 :
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 : */
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 : */
230 2216 : entry = (MemoryContextId *) hash_search(context_id_lookup, &cur,
231 : HASH_ENTER, &found);
232 2216 : entry->context_id = context_id++;
233 : 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 :
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
267 13 : pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
268 : {
269 13 : int pid = PG_GETARG_INT32(0);
270 : PGPROC *proc;
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)
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 : */
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 : */
295 0 : ereport(WARNING,
296 : (errmsg("PID %d is not a PostgreSQL server process", pid)));
297 0 : PG_RETURN_BOOL(false);
298 : }
299 :
300 13 : procNumber = GetNumberFromPGProc(proc);
301 13 : if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, procNumber) < 0)
302 : {
303 : /* Again, just a warning to allow loops */
304 0 : ereport(WARNING,
305 : (errmsg("could not send signal to process %d: %m", pid)));
306 0 : PG_RETURN_BOOL(false);
307 : }
308 :
309 13 : PG_RETURN_BOOL(true);
310 : }
|