Coverage Report

Created: 2025-09-27 06:52

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