/*------------------------------------------------------------------------- * * mcxtfuncs.c * Functions to show backend memory context. * * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/utils/adt/mcxtfuncs.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "funcapi.h" #include "miscadmin.h" #include "mb/pg_wchar.h" #include "storage/proc.h" #include "storage/procarray.h" #include "utils/builtins.h" /* ---------- * The max bytes for showing identifiers of MemoryContext. * ---------- */ #define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE 1024 /* * PutMemoryContextsStatsTupleStore * One recursion level for pg_get_backend_memory_contexts. */ static void PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore, TupleDesc tupdesc, MemoryContext context, const char *parent, int level) { #define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS 9 Datum values[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS]; bool nulls[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS]; MemoryContextCounters stat; MemoryContext child; const char *name; const char *ident; AssertArg(MemoryContextIsValid(context)); name = context->name; ident = context->ident; /* * To be consistent with logging output, we label dynahash contexts with * just the hash table name as with MemoryContextStatsPrint(). */ if (ident && strcmp(name, "dynahash") == 0) { name = ident; ident = NULL; } /* Examine the context itself */ memset(&stat, 0, sizeof(stat)); (*context->methods->stats) (context, NULL, (void *) &level, &stat, true); memset(values, 0, sizeof(values)); memset(nulls, 0, sizeof(nulls)); if (name) values[0] = CStringGetTextDatum(name); else nulls[0] = true; if (ident) { int idlen = strlen(ident); char clipped_ident[MEMORY_CONTEXT_IDENT_DISPLAY_SIZE]; /* * Some identifiers such as SQL query string can be very long, * truncate oversize identifiers. */ if (idlen >= MEMORY_CONTEXT_IDENT_DISPLAY_SIZE) idlen = pg_mbcliplen(ident, idlen, MEMORY_CONTEXT_IDENT_DISPLAY_SIZE - 1); memcpy(clipped_ident, ident, idlen); clipped_ident[idlen] = '\0'; values[1] = CStringGetTextDatum(clipped_ident); } else nulls[1] = true; if (parent) values[2] = CStringGetTextDatum(parent); else nulls[2] = true; values[3] = Int32GetDatum(level); values[4] = Int64GetDatum(stat.totalspace); values[5] = Int64GetDatum(stat.nblocks); values[6] = Int64GetDatum(stat.freespace); values[7] = Int64GetDatum(stat.freechunks); values[8] = Int64GetDatum(stat.totalspace - stat.freespace); tuplestore_putvalues(tupstore, tupdesc, values, nulls); for (child = context->firstchild; child != NULL; child = child->nextchild) { PutMemoryContextsStatsTupleStore(tupstore, tupdesc, child, name, level + 1); } } /* * pg_get_backend_memory_contexts * SQL SRF showing backend memory context. */ Datum pg_get_backend_memory_contexts(PG_FUNCTION_ARGS) { ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; TupleDesc tupdesc; Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; /* check to see if caller supports us returning a tuplestore */ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); if (!(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("materialize mode required, but it is not allowed in this context"))); /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); tupstore = tuplestore_begin_heap(true, false, work_mem); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; MemoryContextSwitchTo(oldcontext); PutMemoryContextsStatsTupleStore(tupstore, tupdesc, TopMemoryContext, NULL, 0); /* clean up and return the tuplestore */ tuplestore_donestoring(tupstore); return (Datum) 0; } /* * pg_log_backend_memory_contexts * Signal a backend process to log its memory contexts. * * Only superusers are allowed to signal to log the memory contexts * because allowing any users to issue this request at an unbounded * rate would cause lots of log messages and which can lead to * denial of service. * * On receipt of this signal, a backend sets the flag in the signal * handler, which causes the next CHECK_FOR_INTERRUPTS() to log the * memory contexts. */ Datum pg_log_backend_memory_contexts(PG_FUNCTION_ARGS) { int pid = PG_GETARG_INT32(0); PGPROC *proc; /* Only allow superusers to log memory contexts. */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be a superuser to log memory contexts"))); proc = BackendPidGetProc(pid); /* * BackendPidGetProc returns NULL if the pid isn't valid; but by the time * we reach kill(), a process for which we get a valid proc here might * have terminated on its own. There's no way to acquire a lock on an * arbitrary process to prevent that. But since this mechanism is usually * used to debug a backend running and consuming lots of memory, that it * might end on its own first and its memory contexts are not logged is * not a problem. */ if (proc == NULL) { /* * This is just a warning so a loop-through-resultset will not abort * if one backend terminated on its own during the run. */ ereport(WARNING, (errmsg("PID %d is not a PostgreSQL server process", pid))); PG_RETURN_BOOL(false); } if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, proc->backendId) < 0) { /* Again, just a warning to allow loops */ ereport(WARNING, (errmsg("could not send signal to process %d: %m", pid))); PG_RETURN_BOOL(false); } PG_RETURN_BOOL(true); }