postgresql/src/backend/utils/activity/backend_status.c

1198 lines
34 KiB
C

/* ----------
* backend_status.c
* Backend status reporting infrastructure.
*
* Copyright (c) 2001-2023, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
* src/backend/utils/activity/backend_status.c
* ----------
*/
#include "postgres.h"
#include "access/xact.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "port/atomics.h" /* for memory barriers */
#include "storage/ipc.h"
#include "storage/proc.h" /* for MyProc */
#include "storage/sinvaladt.h"
#include "utils/ascii.h"
#include "utils/backend_status.h"
#include "utils/guc.h" /* for application_name */
#include "utils/memutils.h"
/* ----------
* Total number of backends including auxiliary
*
* We reserve a slot for each possible BackendId, plus one for each
* possible auxiliary process type. (This scheme assumes there is not
* more than one of any auxiliary process type at a time.) MaxBackends
* includes autovacuum workers and background workers as well.
* ----------
*/
#define NumBackendStatSlots (MaxBackends + NUM_AUXPROCTYPES)
/* ----------
* GUC parameters
* ----------
*/
bool pgstat_track_activities = false;
int pgstat_track_activity_query_size = 1024;
/* exposed so that backend_progress.c can access it */
PgBackendStatus *MyBEEntry = NULL;
static PgBackendStatus *BackendStatusArray = NULL;
static char *BackendAppnameBuffer = NULL;
static char *BackendClientHostnameBuffer = NULL;
static char *BackendActivityBuffer = NULL;
static Size BackendActivityBufferSize = 0;
#ifdef USE_SSL
static PgBackendSSLStatus *BackendSslStatusBuffer = NULL;
#endif
#ifdef ENABLE_GSS
static PgBackendGSSStatus *BackendGssStatusBuffer = NULL;
#endif
/* Status for backends including auxiliary */
static LocalPgBackendStatus *localBackendStatusTable = NULL;
/* Total number of backends including auxiliary */
static int localNumBackends = 0;
static MemoryContext backendStatusSnapContext;
static void pgstat_beshutdown_hook(int code, Datum arg);
static void pgstat_read_current_status(void);
static void pgstat_setup_backend_status_context(void);
/*
* Report shared-memory space needed by CreateSharedBackendStatus.
*/
Size
BackendStatusShmemSize(void)
{
Size size;
/* BackendStatusArray: */
size = mul_size(sizeof(PgBackendStatus), NumBackendStatSlots);
/* BackendAppnameBuffer: */
size = add_size(size,
mul_size(NAMEDATALEN, NumBackendStatSlots));
/* BackendClientHostnameBuffer: */
size = add_size(size,
mul_size(NAMEDATALEN, NumBackendStatSlots));
/* BackendActivityBuffer: */
size = add_size(size,
mul_size(pgstat_track_activity_query_size, NumBackendStatSlots));
#ifdef USE_SSL
/* BackendSslStatusBuffer: */
size = add_size(size,
mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots));
#endif
#ifdef ENABLE_GSS
/* BackendGssStatusBuffer: */
size = add_size(size,
mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots));
#endif
return size;
}
/*
* Initialize the shared status array and several string buffers
* during postmaster startup.
*/
void
CreateSharedBackendStatus(void)
{
Size size;
bool found;
int i;
char *buffer;
/* Create or attach to the shared array */
size = mul_size(sizeof(PgBackendStatus), NumBackendStatSlots);
BackendStatusArray = (PgBackendStatus *)
ShmemInitStruct("Backend Status Array", size, &found);
if (!found)
{
/*
* We're the first - initialize.
*/
MemSet(BackendStatusArray, 0, size);
}
/* Create or attach to the shared appname buffer */
size = mul_size(NAMEDATALEN, NumBackendStatSlots);
BackendAppnameBuffer = (char *)
ShmemInitStruct("Backend Application Name Buffer", size, &found);
if (!found)
{
MemSet(BackendAppnameBuffer, 0, size);
/* Initialize st_appname pointers. */
buffer = BackendAppnameBuffer;
for (i = 0; i < NumBackendStatSlots; i++)
{
BackendStatusArray[i].st_appname = buffer;
buffer += NAMEDATALEN;
}
}
/* Create or attach to the shared client hostname buffer */
size = mul_size(NAMEDATALEN, NumBackendStatSlots);
BackendClientHostnameBuffer = (char *)
ShmemInitStruct("Backend Client Host Name Buffer", size, &found);
if (!found)
{
MemSet(BackendClientHostnameBuffer, 0, size);
/* Initialize st_clienthostname pointers. */
buffer = BackendClientHostnameBuffer;
for (i = 0; i < NumBackendStatSlots; i++)
{
BackendStatusArray[i].st_clienthostname = buffer;
buffer += NAMEDATALEN;
}
}
/* Create or attach to the shared activity buffer */
BackendActivityBufferSize = mul_size(pgstat_track_activity_query_size,
NumBackendStatSlots);
BackendActivityBuffer = (char *)
ShmemInitStruct("Backend Activity Buffer",
BackendActivityBufferSize,
&found);
if (!found)
{
MemSet(BackendActivityBuffer, 0, BackendActivityBufferSize);
/* Initialize st_activity pointers. */
buffer = BackendActivityBuffer;
for (i = 0; i < NumBackendStatSlots; i++)
{
BackendStatusArray[i].st_activity_raw = buffer;
buffer += pgstat_track_activity_query_size;
}
}
#ifdef USE_SSL
/* Create or attach to the shared SSL status buffer */
size = mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots);
BackendSslStatusBuffer = (PgBackendSSLStatus *)
ShmemInitStruct("Backend SSL Status Buffer", size, &found);
if (!found)
{
PgBackendSSLStatus *ptr;
MemSet(BackendSslStatusBuffer, 0, size);
/* Initialize st_sslstatus pointers. */
ptr = BackendSslStatusBuffer;
for (i = 0; i < NumBackendStatSlots; i++)
{
BackendStatusArray[i].st_sslstatus = ptr;
ptr++;
}
}
#endif
#ifdef ENABLE_GSS
/* Create or attach to the shared GSSAPI status buffer */
size = mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots);
BackendGssStatusBuffer = (PgBackendGSSStatus *)
ShmemInitStruct("Backend GSS Status Buffer", size, &found);
if (!found)
{
PgBackendGSSStatus *ptr;
MemSet(BackendGssStatusBuffer, 0, size);
/* Initialize st_gssstatus pointers. */
ptr = BackendGssStatusBuffer;
for (i = 0; i < NumBackendStatSlots; i++)
{
BackendStatusArray[i].st_gssstatus = ptr;
ptr++;
}
}
#endif
}
/*
* Initialize pgstats backend activity state, and set up our on-proc-exit
* hook. Called from InitPostgres and AuxiliaryProcessMain. For auxiliary
* process, MyBackendId is invalid. Otherwise, MyBackendId must be set, but we
* must not have started any transaction yet (since the exit hook must run
* after the last transaction exit).
*
* NOTE: MyDatabaseId isn't set yet; so the shutdown hook has to be careful.
*/
void
pgstat_beinit(void)
{
/* Initialize MyBEEntry */
if (MyBackendId != InvalidBackendId)
{
Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
MyBEEntry = &BackendStatusArray[MyBackendId - 1];
}
else
{
/* Must be an auxiliary process */
Assert(MyAuxProcType != NotAnAuxProcess);
/*
* Assign the MyBEEntry for an auxiliary process. Since it doesn't
* have a BackendId, the slot is statically allocated based on the
* auxiliary process type (MyAuxProcType). Backends use slots indexed
* in the range from 1 to MaxBackends (inclusive), so we use
* MaxBackends + AuxBackendType + 1 as the index of the slot for an
* auxiliary process.
*/
MyBEEntry = &BackendStatusArray[MaxBackends + MyAuxProcType];
}
/* Set up a process-exit hook to clean up */
on_shmem_exit(pgstat_beshutdown_hook, 0);
}
/* ----------
* pgstat_bestart() -
*
* Initialize this backend's entry in the PgBackendStatus array.
* Called from InitPostgres.
*
* Apart from auxiliary processes, MyBackendId, MyDatabaseId,
* session userid, and application_name must be set for a
* backend (hence, this cannot be combined with pgstat_beinit).
* Note also that we must be inside a transaction if this isn't an aux
* process, as we may need to do encoding conversion on some strings.
* ----------
*/
void
pgstat_bestart(void)
{
volatile PgBackendStatus *vbeentry = MyBEEntry;
PgBackendStatus lbeentry;
#ifdef USE_SSL
PgBackendSSLStatus lsslstatus;
#endif
#ifdef ENABLE_GSS
PgBackendGSSStatus lgssstatus;
#endif
/* pgstats state must be initialized from pgstat_beinit() */
Assert(vbeentry != NULL);
/*
* To minimize the time spent modifying the PgBackendStatus entry, and
* avoid risk of errors inside the critical section, we first copy the
* shared-memory struct to a local variable, then modify the data in the
* local variable, then copy the local variable back to shared memory.
* Only the last step has to be inside the critical section.
*
* Most of the data we copy from shared memory is just going to be
* overwritten, but the struct's not so large that it's worth the
* maintenance hassle to copy only the needful fields.
*/
memcpy(&lbeentry,
unvolatize(PgBackendStatus *, vbeentry),
sizeof(PgBackendStatus));
/* These structs can just start from zeroes each time, though */
#ifdef USE_SSL
memset(&lsslstatus, 0, sizeof(lsslstatus));
#endif
#ifdef ENABLE_GSS
memset(&lgssstatus, 0, sizeof(lgssstatus));
#endif
/*
* Now fill in all the fields of lbeentry, except for strings that are
* out-of-line data. Those have to be handled separately, below.
*/
lbeentry.st_procpid = MyProcPid;
lbeentry.st_backendType = MyBackendType;
lbeentry.st_proc_start_timestamp = MyStartTimestamp;
lbeentry.st_activity_start_timestamp = 0;
lbeentry.st_state_start_timestamp = 0;
lbeentry.st_xact_start_timestamp = 0;
lbeentry.st_databaseid = MyDatabaseId;
/* We have userid for client-backends, wal-sender and bgworker processes */
if (lbeentry.st_backendType == B_BACKEND
|| lbeentry.st_backendType == B_WAL_SENDER
|| lbeentry.st_backendType == B_BG_WORKER)
lbeentry.st_userid = GetSessionUserId();
else
lbeentry.st_userid = InvalidOid;
/*
* We may not have a MyProcPort (eg, if this is the autovacuum process).
* If so, use all-zeroes client address, which is dealt with specially in
* pg_stat_get_backend_client_addr and pg_stat_get_backend_client_port.
*/
if (MyProcPort)
memcpy(&lbeentry.st_clientaddr, &MyProcPort->raddr,
sizeof(lbeentry.st_clientaddr));
else
MemSet(&lbeentry.st_clientaddr, 0, sizeof(lbeentry.st_clientaddr));
#ifdef USE_SSL
if (MyProcPort && MyProcPort->ssl_in_use)
{
lbeentry.st_ssl = true;
lsslstatus.ssl_bits = be_tls_get_cipher_bits(MyProcPort);
strlcpy(lsslstatus.ssl_version, be_tls_get_version(MyProcPort), NAMEDATALEN);
strlcpy(lsslstatus.ssl_cipher, be_tls_get_cipher(MyProcPort), NAMEDATALEN);
be_tls_get_peer_subject_name(MyProcPort, lsslstatus.ssl_client_dn, NAMEDATALEN);
be_tls_get_peer_serial(MyProcPort, lsslstatus.ssl_client_serial, NAMEDATALEN);
be_tls_get_peer_issuer_name(MyProcPort, lsslstatus.ssl_issuer_dn, NAMEDATALEN);
}
else
{
lbeentry.st_ssl = false;
}
#else
lbeentry.st_ssl = false;
#endif
#ifdef ENABLE_GSS
if (MyProcPort && MyProcPort->gss != NULL)
{
const char *princ = be_gssapi_get_princ(MyProcPort);
lbeentry.st_gss = true;
lgssstatus.gss_auth = be_gssapi_get_auth(MyProcPort);
lgssstatus.gss_enc = be_gssapi_get_enc(MyProcPort);
lgssstatus.gss_deleg = be_gssapi_get_deleg(MyProcPort);
if (princ)
strlcpy(lgssstatus.gss_princ, princ, NAMEDATALEN);
}
else
{
lbeentry.st_gss = false;
}
#else
lbeentry.st_gss = false;
#endif
lbeentry.st_state = STATE_UNDEFINED;
lbeentry.st_progress_command = PROGRESS_COMMAND_INVALID;
lbeentry.st_progress_command_target = InvalidOid;
lbeentry.st_query_id = UINT64CONST(0);
/*
* we don't zero st_progress_param here to save cycles; nobody should
* examine it until st_progress_command has been set to something other
* than PROGRESS_COMMAND_INVALID
*/
/*
* We're ready to enter the critical section that fills the shared-memory
* status entry. We follow the protocol of bumping st_changecount before
* and after; and make sure it's even afterwards. We use a volatile
* pointer here to ensure the compiler doesn't try to get cute.
*/
PGSTAT_BEGIN_WRITE_ACTIVITY(vbeentry);
/* make sure we'll memcpy the same st_changecount back */
lbeentry.st_changecount = vbeentry->st_changecount;
memcpy(unvolatize(PgBackendStatus *, vbeentry),
&lbeentry,
sizeof(PgBackendStatus));
/*
* We can write the out-of-line strings and structs using the pointers
* that are in lbeentry; this saves some de-volatilizing messiness.
*/
lbeentry.st_appname[0] = '\0';
if (MyProcPort && MyProcPort->remote_hostname)
strlcpy(lbeentry.st_clienthostname, MyProcPort->remote_hostname,
NAMEDATALEN);
else
lbeentry.st_clienthostname[0] = '\0';
lbeentry.st_activity_raw[0] = '\0';
/* Also make sure the last byte in each string area is always 0 */
lbeentry.st_appname[NAMEDATALEN - 1] = '\0';
lbeentry.st_clienthostname[NAMEDATALEN - 1] = '\0';
lbeentry.st_activity_raw[pgstat_track_activity_query_size - 1] = '\0';
#ifdef USE_SSL
memcpy(lbeentry.st_sslstatus, &lsslstatus, sizeof(PgBackendSSLStatus));
#endif
#ifdef ENABLE_GSS
memcpy(lbeentry.st_gssstatus, &lgssstatus, sizeof(PgBackendGSSStatus));
#endif
PGSTAT_END_WRITE_ACTIVITY(vbeentry);
/* Update app name to current GUC setting */
if (application_name)
pgstat_report_appname(application_name);
}
/*
* Clear out our entry in the PgBackendStatus array.
*/
static void
pgstat_beshutdown_hook(int code, Datum arg)
{
volatile PgBackendStatus *beentry = MyBEEntry;
/*
* Clear my status entry, following the protocol of bumping st_changecount
* before and after. We use a volatile pointer here to ensure the
* compiler doesn't try to get cute.
*/
PGSTAT_BEGIN_WRITE_ACTIVITY(beentry);
beentry->st_procpid = 0; /* mark invalid */
PGSTAT_END_WRITE_ACTIVITY(beentry);
/* so that functions can check if backend_status.c is up via MyBEEntry */
MyBEEntry = NULL;
}
/*
* Discard any data collected in the current transaction. Any subsequent
* request will cause new snapshots to be read.
*
* This is also invoked during transaction commit or abort to discard the
* no-longer-wanted snapshot.
*/
void
pgstat_clear_backend_activity_snapshot(void)
{
/* Release memory, if any was allocated */
if (backendStatusSnapContext)
{
MemoryContextDelete(backendStatusSnapContext);
backendStatusSnapContext = NULL;
}
/* Reset variables */
localBackendStatusTable = NULL;
localNumBackends = 0;
}
static void
pgstat_setup_backend_status_context(void)
{
if (!backendStatusSnapContext)
backendStatusSnapContext = AllocSetContextCreate(TopMemoryContext,
"Backend Status Snapshot",
ALLOCSET_SMALL_SIZES);
}
/* ----------
* pgstat_report_activity() -
*
* Called from tcop/postgres.c to report what the backend is actually doing
* (but note cmd_str can be NULL for certain cases).
*
* All updates of the status entry follow the protocol of bumping
* st_changecount before and after. We use a volatile pointer here to
* ensure the compiler doesn't try to get cute.
* ----------
*/
void
pgstat_report_activity(BackendState state, const char *cmd_str)
{
volatile PgBackendStatus *beentry = MyBEEntry;
TimestampTz start_timestamp;
TimestampTz current_timestamp;
int len = 0;
TRACE_POSTGRESQL_STATEMENT_STATUS(cmd_str);
if (!beentry)
return;
if (!pgstat_track_activities)
{
if (beentry->st_state != STATE_DISABLED)
{
volatile PGPROC *proc = MyProc;
/*
* track_activities is disabled, but we last reported a
* non-disabled state. As our final update, change the state and
* clear fields we will not be updating anymore.
*/
PGSTAT_BEGIN_WRITE_ACTIVITY(beentry);
beentry->st_state = STATE_DISABLED;
beentry->st_state_start_timestamp = 0;
beentry->st_activity_raw[0] = '\0';
beentry->st_activity_start_timestamp = 0;
/* st_xact_start_timestamp and wait_event_info are also disabled */
beentry->st_xact_start_timestamp = 0;
beentry->st_query_id = UINT64CONST(0);
proc->wait_event_info = 0;
PGSTAT_END_WRITE_ACTIVITY(beentry);
}
return;
}
/*
* To minimize the time spent modifying the entry, and avoid risk of
* errors inside the critical section, fetch all the needed data first.
*/
start_timestamp = GetCurrentStatementStartTimestamp();
if (cmd_str != NULL)
{
/*
* Compute length of to-be-stored string unaware of multi-byte
* characters. For speed reasons that'll get corrected on read, rather
* than computed every write.
*/
len = Min(strlen(cmd_str), pgstat_track_activity_query_size - 1);
}
current_timestamp = GetCurrentTimestamp();
/*
* If the state has changed from "active" or "idle in transaction",
* calculate the duration.
*/
if ((beentry->st_state == STATE_RUNNING ||
beentry->st_state == STATE_FASTPATH ||
beentry->st_state == STATE_IDLEINTRANSACTION ||
beentry->st_state == STATE_IDLEINTRANSACTION_ABORTED) &&
state != beentry->st_state)
{
long secs;
int usecs;
TimestampDifference(beentry->st_state_start_timestamp,
current_timestamp,
&secs, &usecs);
if (beentry->st_state == STATE_RUNNING ||
beentry->st_state == STATE_FASTPATH)
pgstat_count_conn_active_time((PgStat_Counter) secs * 1000000 + usecs);
else
pgstat_count_conn_txn_idle_time((PgStat_Counter) secs * 1000000 + usecs);
}
/*
* Now update the status entry
*/
PGSTAT_BEGIN_WRITE_ACTIVITY(beentry);
beentry->st_state = state;
beentry->st_state_start_timestamp = current_timestamp;
/*
* If a new query is started, we reset the query identifier as it'll only
* be known after parse analysis, to avoid reporting last query's
* identifier.
*/
if (state == STATE_RUNNING)
beentry->st_query_id = UINT64CONST(0);
if (cmd_str != NULL)
{
memcpy((char *) beentry->st_activity_raw, cmd_str, len);
beentry->st_activity_raw[len] = '\0';
beentry->st_activity_start_timestamp = start_timestamp;
}
PGSTAT_END_WRITE_ACTIVITY(beentry);
}
/* --------
* pgstat_report_query_id() -
*
* Called to update top-level query identifier.
* --------
*/
void
pgstat_report_query_id(uint64 query_id, bool force)
{
volatile PgBackendStatus *beentry = MyBEEntry;
/*
* if track_activities is disabled, st_query_id should already have been
* reset
*/
if (!beentry || !pgstat_track_activities)
return;
/*
* We only report the top-level query identifiers. The stored query_id is
* reset when a backend calls pgstat_report_activity(STATE_RUNNING), or
* with an explicit call to this function using the force flag. If the
* saved query identifier is not zero it means that it's not a top-level
* command, so ignore the one provided unless it's an explicit call to
* reset the identifier.
*/
if (beentry->st_query_id != 0 && !force)
return;
/*
* Update my status entry, following the protocol of bumping
* st_changecount before and after. We use a volatile pointer here to
* ensure the compiler doesn't try to get cute.
*/
PGSTAT_BEGIN_WRITE_ACTIVITY(beentry);
beentry->st_query_id = query_id;
PGSTAT_END_WRITE_ACTIVITY(beentry);
}
/* ----------
* pgstat_report_appname() -
*
* Called to update our application name.
* ----------
*/
void
pgstat_report_appname(const char *appname)
{
volatile PgBackendStatus *beentry = MyBEEntry;
int len;
if (!beentry)
return;
/* This should be unnecessary if GUC did its job, but be safe */
len = pg_mbcliplen(appname, strlen(appname), NAMEDATALEN - 1);
/*
* Update my status entry, following the protocol of bumping
* st_changecount before and after. We use a volatile pointer here to
* ensure the compiler doesn't try to get cute.
*/
PGSTAT_BEGIN_WRITE_ACTIVITY(beentry);
memcpy((char *) beentry->st_appname, appname, len);
beentry->st_appname[len] = '\0';
PGSTAT_END_WRITE_ACTIVITY(beentry);
}
/*
* Report current transaction start timestamp as the specified value.
* Zero means there is no active transaction.
*/
void
pgstat_report_xact_timestamp(TimestampTz tstamp)
{
volatile PgBackendStatus *beentry = MyBEEntry;
if (!pgstat_track_activities || !beentry)
return;
/*
* Update my status entry, following the protocol of bumping
* st_changecount before and after. We use a volatile pointer here to
* ensure the compiler doesn't try to get cute.
*/
PGSTAT_BEGIN_WRITE_ACTIVITY(beentry);
beentry->st_xact_start_timestamp = tstamp;
PGSTAT_END_WRITE_ACTIVITY(beentry);
}
/* ----------
* pgstat_read_current_status() -
*
* Copy the current contents of the PgBackendStatus array to local memory,
* if not already done in this transaction.
* ----------
*/
static void
pgstat_read_current_status(void)
{
volatile PgBackendStatus *beentry;
LocalPgBackendStatus *localtable;
LocalPgBackendStatus *localentry;
char *localappname,
*localclienthostname,
*localactivity;
#ifdef USE_SSL
PgBackendSSLStatus *localsslstatus;
#endif
#ifdef ENABLE_GSS
PgBackendGSSStatus *localgssstatus;
#endif
int i;
if (localBackendStatusTable)
return; /* already done */
pgstat_setup_backend_status_context();
/*
* Allocate storage for local copy of state data. We can presume that
* none of these requests overflow size_t, because we already calculated
* the same values using mul_size during shmem setup. However, with
* probably-silly values of pgstat_track_activity_query_size and
* max_connections, the localactivity buffer could exceed 1GB, so use
* "huge" allocation for that one.
*/
localtable = (LocalPgBackendStatus *)
MemoryContextAlloc(backendStatusSnapContext,
sizeof(LocalPgBackendStatus) * NumBackendStatSlots);
localappname = (char *)
MemoryContextAlloc(backendStatusSnapContext,
NAMEDATALEN * NumBackendStatSlots);
localclienthostname = (char *)
MemoryContextAlloc(backendStatusSnapContext,
NAMEDATALEN * NumBackendStatSlots);
localactivity = (char *)
MemoryContextAllocHuge(backendStatusSnapContext,
pgstat_track_activity_query_size * NumBackendStatSlots);
#ifdef USE_SSL
localsslstatus = (PgBackendSSLStatus *)
MemoryContextAlloc(backendStatusSnapContext,
sizeof(PgBackendSSLStatus) * NumBackendStatSlots);
#endif
#ifdef ENABLE_GSS
localgssstatus = (PgBackendGSSStatus *)
MemoryContextAlloc(backendStatusSnapContext,
sizeof(PgBackendGSSStatus) * NumBackendStatSlots);
#endif
localNumBackends = 0;
beentry = BackendStatusArray;
localentry = localtable;
for (i = 1; i <= NumBackendStatSlots; i++)
{
/*
* Follow the protocol of retrying if st_changecount changes while we
* copy the entry, or if it's odd. (The check for odd is needed to
* cover the case where we are able to completely copy the entry while
* the source backend is between increment steps.) We use a volatile
* pointer here to ensure the compiler doesn't try to get cute.
*/
for (;;)
{
int before_changecount;
int after_changecount;
pgstat_begin_read_activity(beentry, before_changecount);
localentry->backendStatus.st_procpid = beentry->st_procpid;
/* Skip all the data-copying work if entry is not in use */
if (localentry->backendStatus.st_procpid > 0)
{
memcpy(&localentry->backendStatus, unvolatize(PgBackendStatus *, beentry), sizeof(PgBackendStatus));
/*
* For each PgBackendStatus field that is a pointer, copy the
* pointed-to data, then adjust the local copy of the pointer
* field to point at the local copy of the data.
*
* strcpy is safe even if the string is modified concurrently,
* because there's always a \0 at the end of the buffer.
*/
strcpy(localappname, (char *) beentry->st_appname);
localentry->backendStatus.st_appname = localappname;
strcpy(localclienthostname, (char *) beentry->st_clienthostname);
localentry->backendStatus.st_clienthostname = localclienthostname;
strcpy(localactivity, (char *) beentry->st_activity_raw);
localentry->backendStatus.st_activity_raw = localactivity;
#ifdef USE_SSL
if (beentry->st_ssl)
{
memcpy(localsslstatus, beentry->st_sslstatus, sizeof(PgBackendSSLStatus));
localentry->backendStatus.st_sslstatus = localsslstatus;
}
#endif
#ifdef ENABLE_GSS
if (beentry->st_gss)
{
memcpy(localgssstatus, beentry->st_gssstatus, sizeof(PgBackendGSSStatus));
localentry->backendStatus.st_gssstatus = localgssstatus;
}
#endif
}
pgstat_end_read_activity(beentry, after_changecount);
if (pgstat_read_activity_complete(before_changecount,
after_changecount))
break;
/* Make sure we can break out of loop if stuck... */
CHECK_FOR_INTERRUPTS();
}
/* Only valid entries get included into the local array */
if (localentry->backendStatus.st_procpid > 0)
{
/*
* The BackendStatusArray index is exactly the BackendId of the
* source backend. Note that this means localBackendStatusTable
* is in order by backend_id. pgstat_fetch_stat_beentry() depends
* on that.
*/
localentry->backend_id = i;
BackendIdGetTransactionIds(i,
&localentry->backend_xid,
&localentry->backend_xmin,
&localentry->backend_subxact_count,
&localentry->backend_subxact_overflowed);
localentry++;
localappname += NAMEDATALEN;
localclienthostname += NAMEDATALEN;
localactivity += pgstat_track_activity_query_size;
#ifdef USE_SSL
localsslstatus++;
#endif
#ifdef ENABLE_GSS
localgssstatus++;
#endif
localNumBackends++;
}
beentry++;
}
/* Set the pointer only after completion of a valid table */
localBackendStatusTable = localtable;
}
/* ----------
* pgstat_get_backend_current_activity() -
*
* Return a string representing the current activity of the backend with
* the specified PID. This looks directly at the BackendStatusArray,
* and so will provide current information regardless of the age of our
* transaction's snapshot of the status array.
*
* It is the caller's responsibility to invoke this only for backends whose
* state is expected to remain stable while the result is in use. The
* only current use is in deadlock reporting, where we can expect that
* the target backend is blocked on a lock. (There are corner cases
* where the target's wait could get aborted while we are looking at it,
* but the very worst consequence is to return a pointer to a string
* that's been changed, so we won't worry too much.)
*
* Note: return strings for special cases match pg_stat_get_backend_activity.
* ----------
*/
const char *
pgstat_get_backend_current_activity(int pid, bool checkUser)
{
PgBackendStatus *beentry;
int i;
beentry = BackendStatusArray;
for (i = 1; i <= MaxBackends; i++)
{
/*
* Although we expect the target backend's entry to be stable, that
* doesn't imply that anyone else's is. To avoid identifying the
* wrong backend, while we check for a match to the desired PID we
* must follow the protocol of retrying if st_changecount changes
* while we examine the entry, or if it's odd. (This might be
* unnecessary, since fetching or storing an int is almost certainly
* atomic, but let's play it safe.) We use a volatile pointer here to
* ensure the compiler doesn't try to get cute.
*/
volatile PgBackendStatus *vbeentry = beentry;
bool found;
for (;;)
{
int before_changecount;
int after_changecount;
pgstat_begin_read_activity(vbeentry, before_changecount);
found = (vbeentry->st_procpid == pid);
pgstat_end_read_activity(vbeentry, after_changecount);
if (pgstat_read_activity_complete(before_changecount,
after_changecount))
break;
/* Make sure we can break out of loop if stuck... */
CHECK_FOR_INTERRUPTS();
}
if (found)
{
/* Now it is safe to use the non-volatile pointer */
if (checkUser && !superuser() && beentry->st_userid != GetUserId())
return "<insufficient privilege>";
else if (*(beentry->st_activity_raw) == '\0')
return "<command string not enabled>";
else
{
/* this'll leak a bit of memory, but that seems acceptable */
return pgstat_clip_activity(beentry->st_activity_raw);
}
}
beentry++;
}
/* If we get here, caller is in error ... */
return "<backend information not available>";
}
/* ----------
* pgstat_get_crashed_backend_activity() -
*
* Return a string representing the current activity of the backend with
* the specified PID. Like the function above, but reads shared memory with
* the expectation that it may be corrupt. On success, copy the string
* into the "buffer" argument and return that pointer. On failure,
* return NULL.
*
* This function is only intended to be used by the postmaster to report the
* query that crashed a backend. In particular, no attempt is made to
* follow the correct concurrency protocol when accessing the
* BackendStatusArray. But that's OK, in the worst case we'll return a
* corrupted message. We also must take care not to trip on ereport(ERROR).
* ----------
*/
const char *
pgstat_get_crashed_backend_activity(int pid, char *buffer, int buflen)
{
volatile PgBackendStatus *beentry;
int i;
beentry = BackendStatusArray;
/*
* We probably shouldn't get here before shared memory has been set up,
* but be safe.
*/
if (beentry == NULL || BackendActivityBuffer == NULL)
return NULL;
for (i = 1; i <= MaxBackends; i++)
{
if (beentry->st_procpid == pid)
{
/* Read pointer just once, so it can't change after validation */
const char *activity = beentry->st_activity_raw;
const char *activity_last;
/*
* We mustn't access activity string before we verify that it
* falls within the BackendActivityBuffer. To make sure that the
* entire string including its ending is contained within the
* buffer, subtract one activity length from the buffer size.
*/
activity_last = BackendActivityBuffer + BackendActivityBufferSize
- pgstat_track_activity_query_size;
if (activity < BackendActivityBuffer ||
activity > activity_last)
return NULL;
/* If no string available, no point in a report */
if (activity[0] == '\0')
return NULL;
/*
* Copy only ASCII-safe characters so we don't run into encoding
* problems when reporting the message; and be sure not to run off
* the end of memory. As only ASCII characters are reported, it
* doesn't seem necessary to perform multibyte aware clipping.
*/
ascii_safe_strlcpy(buffer, activity,
Min(buflen, pgstat_track_activity_query_size));
return buffer;
}
beentry++;
}
/* PID not found */
return NULL;
}
/* ----------
* pgstat_get_my_query_id() -
*
* Return current backend's query identifier.
*/
uint64
pgstat_get_my_query_id(void)
{
if (!MyBEEntry)
return 0;
/*
* There's no need for a lock around pgstat_begin_read_activity /
* pgstat_end_read_activity here as it's only called from
* pg_stat_get_activity which is already protected, or from the same
* backend which means that there won't be concurrent writes.
*/
return MyBEEntry->st_query_id;
}
/* ----------
* cmp_lbestatus
*
* Comparison function for bsearch() on an array of LocalPgBackendStatus.
* The backend_id field is used to compare the arguments.
* ----------
*/
static int
cmp_lbestatus(const void *a, const void *b)
{
const LocalPgBackendStatus *lbestatus1 = (const LocalPgBackendStatus *) a;
const LocalPgBackendStatus *lbestatus2 = (const LocalPgBackendStatus *) b;
return lbestatus1->backend_id - lbestatus2->backend_id;
}
/* ----------
* pgstat_fetch_stat_beentry() -
*
* Support function for the SQL-callable pgstat* functions. Returns
* our local copy of the current-activity entry for one backend,
* or NULL if the given beid doesn't identify any known session.
*
* The beid argument is the BackendId of the desired session
* (note that this is unlike pgstat_fetch_stat_local_beentry()).
*
* NB: caller is responsible for a check if the user is permitted to see
* this info (especially the querystring).
* ----------
*/
PgBackendStatus *
pgstat_fetch_stat_beentry(BackendId beid)
{
LocalPgBackendStatus key;
LocalPgBackendStatus *ret;
pgstat_read_current_status();
/*
* Since the localBackendStatusTable is in order by backend_id, we can use
* bsearch() to search it efficiently.
*/
key.backend_id = beid;
ret = (LocalPgBackendStatus *) bsearch(&key, localBackendStatusTable,
localNumBackends,
sizeof(LocalPgBackendStatus),
cmp_lbestatus);
if (ret)
return &ret->backendStatus;
return NULL;
}
/* ----------
* pgstat_fetch_stat_local_beentry() -
*
* Like pgstat_fetch_stat_beentry() but with locally computed additions (like
* xid and xmin values of the backend)
*
* The beid argument is a 1-based index in the localBackendStatusTable
* (note that this is unlike pgstat_fetch_stat_beentry()).
* Returns NULL if the argument is out of range (no current caller does that).
*
* NB: caller is responsible for a check if the user is permitted to see
* this info (especially the querystring).
* ----------
*/
LocalPgBackendStatus *
pgstat_fetch_stat_local_beentry(int beid)
{
pgstat_read_current_status();
if (beid < 1 || beid > localNumBackends)
return NULL;
return &localBackendStatusTable[beid - 1];
}
/* ----------
* pgstat_fetch_stat_numbackends() -
*
* Support function for the SQL-callable pgstat* functions. Returns
* the number of sessions known in the localBackendStatusTable, i.e.
* the maximum 1-based index to pass to pgstat_fetch_stat_local_beentry().
* ----------
*/
int
pgstat_fetch_stat_numbackends(void)
{
pgstat_read_current_status();
return localNumBackends;
}
/*
* Convert a potentially unsafely truncated activity string (see
* PgBackendStatus.st_activity_raw's documentation) into a correctly truncated
* one.
*
* The returned string is allocated in the caller's memory context and may be
* freed.
*/
char *
pgstat_clip_activity(const char *raw_activity)
{
char *activity;
int rawlen;
int cliplen;
/*
* Some callers, like pgstat_get_backend_current_activity(), do not
* guarantee that the buffer isn't concurrently modified. We try to take
* care that the buffer is always terminated by a NUL byte regardless, but
* let's still be paranoid about the string's length. In those cases the
* underlying buffer is guaranteed to be pgstat_track_activity_query_size
* large.
*/
activity = pnstrdup(raw_activity, pgstat_track_activity_query_size - 1);
/* now double-guaranteed to be NUL terminated */
rawlen = strlen(activity);
/*
* All supported server-encodings make it possible to determine the length
* of a multi-byte character from its first byte (this is not the case for
* client encodings, see GB18030). As st_activity is always stored using
* server encoding, this allows us to perform multi-byte aware truncation,
* even if the string earlier was truncated in the middle of a multi-byte
* character.
*/
cliplen = pg_mbcliplen(activity, rawlen,
pgstat_track_activity_query_size - 1);
activity[cliplen] = '\0';
return activity;
}