Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * mcxtfuncs.c
4 : * Functions to show backend memory context.
5 : *
6 : * Portions Copyright (c) 1996-2024, 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 3126 : int_list_to_array(const List *list)
49 : {
50 : Datum *datum_array;
51 : int length;
52 : ArrayType *result_array;
53 :
54 3126 : length = list_length(list);
55 3126 : datum_array = (Datum *) palloc(length * sizeof(Datum));
56 :
57 15486 : foreach_int(i, list)
58 9234 : datum_array[foreach_current_index(i)] = Int32GetDatum(i);
59 :
60 3126 : result_array = construct_array_builtin(datum_array, length, INT4OID);
61 :
62 3126 : return PointerGetDatum(result_array);
63 : }
64 :
65 : /*
66 : * PutMemoryContextsStatsTupleStore
67 : * Add details for the given MemoryContext to 'tupstore'.
68 : */
69 : static void
70 3126 : 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;
79 3126 : List *path = NIL;
80 : const char *name;
81 : const char *ident;
82 : const char *type;
83 :
84 : Assert(MemoryContextIsValid(context));
85 :
86 : /*
87 : * Figure out the transient context_id of this context and each of its
88 : * ancestors.
89 : */
90 12360 : for (MemoryContext cur = context; cur != NULL; cur = cur->parent)
91 : {
92 : MemoryContextId *entry;
93 : bool found;
94 :
95 9234 : entry = hash_search(context_id_lookup, &cur, HASH_FIND, &found);
96 :
97 9234 : if (!found)
98 0 : elog(ERROR, "hash table corrupted");
99 9234 : path = lcons_int(entry->context_id, path);
100 : }
101 :
102 : /* Examine the context itself */
103 3126 : memset(&stat, 0, sizeof(stat));
104 3126 : (*context->methods->stats) (context, NULL, NULL, &stat, true);
105 :
106 3126 : memset(values, 0, sizeof(values));
107 3126 : memset(nulls, 0, sizeof(nulls));
108 :
109 3126 : name = context->name;
110 3126 : 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 3126 : if (ident && strcmp(name, "dynahash") == 0)
117 : {
118 300 : name = ident;
119 300 : ident = NULL;
120 : }
121 :
122 3126 : if (name)
123 3126 : values[0] = CStringGetTextDatum(name);
124 : else
125 0 : nulls[0] = true;
126 :
127 3126 : if (ident)
128 : {
129 2250 : 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 : */
136 2250 : if (idlen >= MEMORY_CONTEXT_IDENT_DISPLAY_SIZE)
137 0 : idlen = pg_mbcliplen(ident, idlen, MEMORY_CONTEXT_IDENT_DISPLAY_SIZE - 1);
138 :
139 2250 : memcpy(clipped_ident, ident, idlen);
140 2250 : clipped_ident[idlen] = '\0';
141 2250 : values[1] = CStringGetTextDatum(clipped_ident);
142 : }
143 : else
144 876 : nulls[1] = true;
145 :
146 3126 : switch (context->type)
147 : {
148 3090 : case T_AllocSetContext:
149 3090 : type = "AllocSet";
150 3090 : break;
151 30 : case T_GenerationContext:
152 30 : type = "Generation";
153 30 : break;
154 0 : case T_SlabContext:
155 0 : type = "Slab";
156 0 : break;
157 6 : case T_BumpContext:
158 6 : type = "Bump";
159 6 : break;
160 0 : default:
161 0 : type = "???";
162 0 : break;
163 : }
164 :
165 3126 : values[2] = CStringGetTextDatum(type);
166 3126 : values[3] = Int32GetDatum(list_length(path)); /* level */
167 3126 : values[4] = int_list_to_array(path);
168 3126 : values[5] = Int64GetDatum(stat.totalspace);
169 3126 : values[6] = Int64GetDatum(stat.nblocks);
170 3126 : values[7] = Int64GetDatum(stat.freespace);
171 3126 : values[8] = Int64GetDatum(stat.freechunks);
172 3126 : values[9] = Int64GetDatum(stat.totalspace - stat.freespace);
173 :
174 3126 : tuplestore_putvalues(tupstore, tupdesc, values, nulls);
175 3126 : list_free(path);
176 3126 : }
177 :
178 : /*
179 : * pg_get_backend_memory_contexts
180 : * SQL SRF showing backend memory context.
181 : */
182 : Datum
183 24 : pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
184 : {
185 24 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
186 : int context_id;
187 : List *contexts;
188 : HASHCTL ctl;
189 : HTAB *context_id_lookup;
190 :
191 24 : ctl.keysize = sizeof(MemoryContext);
192 24 : ctl.entrysize = sizeof(MemoryContextId);
193 24 : ctl.hcxt = CurrentMemoryContext;
194 :
195 24 : context_id_lookup = hash_create("pg_get_backend_memory_contexts",
196 : 256,
197 : &ctl,
198 : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
199 :
200 24 : 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 24 : contexts = list_make1(TopMemoryContext);
213 :
214 : /* TopMemoryContext will always have a context_id of 1 */
215 24 : context_id = 1;
216 :
217 3174 : 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 : */
227 3126 : entry = (MemoryContextId *) hash_search(context_id_lookup, &cur,
228 : HASH_ENTER, &found);
229 3126 : entry->context_id = context_id++;
230 : Assert(!found);
231 :
232 3126 : 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 6228 : for (MemoryContext c = cur->firstchild; c != NULL; c = c->nextchild)
242 3102 : contexts = lappend(contexts, c);
243 : }
244 :
245 24 : hash_destroy(context_id_lookup);
246 :
247 24 : 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
264 18 : pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
265 : {
266 18 : int pid = PG_GETARG_INT32(0);
267 : PGPROC *proc;
268 18 : ProcNumber procNumber = INVALID_PROC_NUMBER;
269 :
270 : /*
271 : * See if the process with given pid is a backend or an auxiliary process.
272 : */
273 18 : proc = BackendPidGetProc(pid);
274 18 : if (proc == NULL)
275 6 : 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 18 : 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 18 : procNumber = GetNumberFromPGProc(proc);
298 18 : 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 18 : PG_RETURN_BOOL(true);
307 : }
|