mirror of
https://github.com/postgres/postgres.git
synced 2025-05-03 22:24:49 +03:00
The use of this function is limited to superusers and the code includes a hardcoded check for that. However, the code would look for the PGPROC entry to signal for the memory dump before checking if the user is a superuser or not, which does not make sense if we know that an error will be returned. Note that the code would let one know if a process was a PostgreSQL process or not even for non-authorized users, which is not the case now, but this avoids taking ProcArrayLock that will most likely finish by being unnecessary. Thanks to Julien Rouhaud and Tom Lane for the discussion. Discussion: https://postgr.es/m/YLxw1uVGIAP5uMPl@paquier.xyz
218 lines
6.1 KiB
C
218 lines
6.1 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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);
|
|
}
|