From 6cd4efa3e21170e66d5bbea318dc8843fadaaa2e Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Tue, 18 Mar 2025 12:57:31 +0700 Subject: [PATCH 01/11] Add profile_extended and history_extended views with additional dimensions Sometimes it can be useful to have additional info collected with wait_events, so we add two new views/functions that include more information. The structure of those views could be changed in new versions of pg_wait_sampling extension --- Makefile | 2 +- collector.c | 119 ++++- expected/queries.out | 45 ++ meson.build | 1 + pg_wait_sampling--1.1--1.2.sql | 79 ++++ pg_wait_sampling.c | 765 +++++++++++++++++++++++++++++++++ pg_wait_sampling.control | 2 +- pg_wait_sampling.h | 63 ++- sql/queries.sql | 14 + 9 files changed, 1067 insertions(+), 23 deletions(-) create mode 100644 pg_wait_sampling--1.1--1.2.sql diff --git a/Makefile b/Makefile index 32711a3..f9de6d9 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ MODULE_big = pg_wait_sampling OBJS = pg_wait_sampling.o collector.o EXTENSION = pg_wait_sampling -DATA = pg_wait_sampling--1.1.sql pg_wait_sampling--1.0--1.1.sql +DATA = pg_wait_sampling--1.1.sql pg_wait_sampling--1.0--1.1.sql pg_wait_sampling--1.1--1.2.sql REGRESS = load queries diff --git a/collector.c b/collector.c index 721299f..e073062 100644 --- a/collector.c +++ b/collector.c @@ -10,6 +10,7 @@ #include "postgres.h" #include +#include #include "compat.h" #include "miscadmin.h" @@ -30,6 +31,13 @@ #include "utils/resowner.h" #include "utils/timestamp.h" +#define check_bestatus_dimensions(dimensions) \ + (dimensions & (PGWS_DIMENSIONS_BE_TYPE |\ + PGWS_DIMENSIONS_BE_STATE |\ + PGWS_DIMENSIONS_BE_START_TIME |\ + PGWS_DIMENSIONS_CLIENT_ADDR |\ + PGWS_DIMENSIONS_CLIENT_HOSTNAME |\ + PGWS_DIMENSIONS_APPNAME)) static volatile sig_atomic_t shutdown_requested = false; static void handle_sigterm(SIGNAL_ARGS); @@ -162,25 +170,103 @@ probe_waits(History *observations, HTAB *profile_hash, LWLockAcquire(ProcArrayLock, LW_SHARED); for (i = 0; i < ProcGlobal->allProcCount; i++) { - HistoryItem item, + HistoryItem item_history, *observation; + ProfileItem item_profile; PGPROC *proc = &ProcGlobal->allProcs[i]; + int pid; + uint32 wait_event_info; - if (!pgws_should_sample_proc(proc, &item.pid, &item.wait_event_info)) + /* Check if we need to sample this process */ + if (!pgws_should_sample_proc(proc, &pid, &wait_event_info)) continue; + /* We zero whole HistoryItem to avoid doing it field-by-field */ + memset(&item_history, 0, sizeof(HistoryItem)); + memset(&item_profile, 0, sizeof(ProfileItem)); + + item_history.pid = pid; + item_profile.pid = pid; + + item_history.wait_event_info = wait_event_info; + item_profile.wait_event_info = wait_event_info; + if (pgws_profileQueries) - item.queryId = pgws_proc_queryids[i]; - else - item.queryId = 0; + { + item_history.queryId = pgws_proc_queryids[i]; + item_profile.queryId = pgws_proc_queryids[i]; + } - item.ts = ts; + item_history.ts = ts; + + /* Copy everything we need from PGPROC */ + if (pgws_history_dimensions & PGWS_DIMENSIONS_ROLE_ID) + item_history.role_id = proc->roleId; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_ROLE_ID) + item_profile.role_id = proc->roleId; + + if (pgws_history_dimensions & PGWS_DIMENSIONS_DB_ID) + item_history.database_id = proc->databaseId; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_DB_ID) + item_profile.database_id = proc->databaseId; + + if (pgws_history_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + item_history.parallel_leader_pid = (proc->lockGroupLeader ? + proc->lockGroupLeader->pid : + 0); + if (pgws_profile_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + item_profile.parallel_leader_pid = (proc->lockGroupLeader ? + proc->lockGroupLeader->pid : + 0); + /* Look into BackendStatus only if necessary */ + if (check_bestatus_dimensions(pgws_history_dimensions) || + check_bestatus_dimensions(pgws_profile_dimensions)) + { +#if PG_VERSION_NUM >= 170000 + PgBackendStatus *bestatus = pgstat_get_beentry_by_proc_number(GetNumberFromPGProc(proc)); +#else + PgBackendStatus *bestatus = get_beentry_by_procpid(proc->pid); +#endif + /* Copy everything we need from BackendStatus */ + if (bestatus) + { + if (pgws_history_dimensions & PGWS_DIMENSIONS_BE_TYPE) + item_history.backend_type = bestatus->st_backendType; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE) + item_profile.backend_type = bestatus->st_backendType; + + if (pgws_history_dimensions & PGWS_DIMENSIONS_BE_STATE) + item_history.backend_state = bestatus->st_state; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE) + item_profile.backend_state = bestatus->st_state; + + if (pgws_history_dimensions & PGWS_DIMENSIONS_BE_START_TIME) + item_history.proc_start = bestatus->st_proc_start_timestamp; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) + item_profile.proc_start = bestatus->st_proc_start_timestamp; + + if (pgws_history_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) + item_history.client_addr = bestatus->st_clientaddr; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) + item_profile.client_addr = bestatus->st_clientaddr; + + if (pgws_history_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + strcpy(item_history.client_hostname, bestatus->st_clienthostname); + if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + strcpy(item_profile.client_hostname, bestatus->st_clienthostname); + + if (pgws_history_dimensions & PGWS_DIMENSIONS_APPNAME) + strcpy(item_history.appname, bestatus->st_appname); + if (pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) + strcpy(item_profile.appname, bestatus->st_appname); + } + } /* Write to the history if needed */ if (write_history) { observation = get_next_observation(observations); - *observation = item; + *observation = item_history; } /* Write to the profile if needed */ @@ -190,9 +276,9 @@ probe_waits(History *observations, HTAB *profile_hash, bool found; if (!profile_pid) - item.pid = 0; + item_profile.pid = 0; - profileItem = (ProfileItem *) hash_search(profile_hash, &item, HASH_ENTER, &found); + profileItem = (ProfileItem *) hash_search(profile_hash, &item_profile, HASH_ENTER, &found); if (found) profileItem->count++; else @@ -200,6 +286,11 @@ probe_waits(History *observations, HTAB *profile_hash, } } LWLockRelease(ProcArrayLock); +#if PG_VERSION_NUM >= 140000 + pgstat_clear_backend_activity_snapshot(); +#else + pgstat_clear_snapshot(); +#endif } /* @@ -287,10 +378,12 @@ make_profile_hash() { HASHCTL hash_ctl; - if (pgws_profileQueries) - hash_ctl.keysize = offsetof(ProfileItem, count); - else - hash_ctl.keysize = offsetof(ProfileItem, queryId); + /* + * Since adding additional dimensions we include everyting except count + * into hashtable key. This is fine for cases when some fields are 0 since + * it doesn't impede our ability to search the hash table for entries + */ + hash_ctl.keysize = offsetof(ProfileItem, count); hash_ctl.entrysize = sizeof(ProfileItem); return hash_create("Waits profile hash", 1024, &hash_ctl, diff --git a/expected/queries.out b/expected/queries.out index 722df5f..6718c14 100644 --- a/expected/queries.out +++ b/expected/queries.out @@ -20,6 +20,27 @@ WITH t as (SELECT sum(0) FROM pg_wait_sampling_profile) 0 (1 row) +WITH t as (SELECT sum(0) FROM pg_wait_sampling_current_extended) + SELECT sum(0) FROM generate_series(1, 2), t; + sum +----- + 0 +(1 row) + +WITH t as (SELECT sum(0) FROM pg_wait_sampling_history_extended) + SELECT sum(0) FROM generate_series(1, 2), t; + sum +----- + 0 +(1 row) + +WITH t as (SELECT sum(0) FROM pg_wait_sampling_profile_extended) + SELECT sum(0) FROM generate_series(1, 2), t; + sum +----- + 0 +(1 row) + -- Some dummy checks just to be sure that all our functions work and return something. SELECT count(*) = 1 as test FROM pg_wait_sampling_get_current(pg_backend_pid()); test @@ -45,4 +66,28 @@ SELECT pg_wait_sampling_reset_profile(); (1 row) +SELECT count(*) = 1 as test FROM pg_wait_sampling_get_current_extended(pg_backend_pid()); + test +------ + t +(1 row) + +SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_profile_extended(); + test +------ + t +(1 row) + +SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_history_extended(); + test +------ + t +(1 row) + +SELECT pg_wait_sampling_reset_profile(); + pg_wait_sampling_reset_profile +-------------------------------- + +(1 row) + DROP EXTENSION pg_wait_sampling; diff --git a/meson.build b/meson.build index c3c3dc9..162bb0e 100644 --- a/meson.build +++ b/meson.build @@ -24,6 +24,7 @@ install_data( 'pg_wait_sampling.control', 'pg_wait_sampling--1.0--1.1.sql', 'pg_wait_sampling--1.1.sql', + 'pg_wait_sampling--1.1--1.2.sql', kwargs: contrib_data_args, ) diff --git a/pg_wait_sampling--1.1--1.2.sql b/pg_wait_sampling--1.1--1.2.sql new file mode 100644 index 0000000..df95826 --- /dev/null +++ b/pg_wait_sampling--1.1--1.2.sql @@ -0,0 +1,79 @@ +/* contrib/pg_wait_sampling/pg_wait_sampling--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_wait_sampling UPDATE TO 1.2" to load this file. \quit + +CREATE FUNCTION pg_wait_sampling_get_current_extended ( + pid int4, + OUT pid int4, + OUT event_type text, + OUT event text, + OUT queryid int8, + OUT role_id int8, + OUT database_id int8, + OUT parallel_leader_pid int4, + OUT backend_type text, + OUT backend_state text, + OUT proc_start timestamptz, + OUT client_addr text, + OUT client_hostname text, + OUT appname text +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C VOLATILE CALLED ON NULL INPUT; + +CREATE VIEW pg_wait_sampling_current_extended AS + SELECT * FROM pg_wait_sampling_get_current_extended(NULL::integer); + +GRANT SELECT ON pg_wait_sampling_current TO PUBLIC; + +CREATE FUNCTION pg_wait_sampling_get_history_extended ( + OUT pid int4, + OUT ts timestamptz, + OUT event_type text, + OUT event text, + OUT queryid int8, + OUT role_id int8, + OUT database_id int8, + OUT parallel_leader_pid int4, + OUT backend_type text, + OUT backend_state text, + OUT proc_start timestamptz, + OUT client_addr text, + OUT client_hostname text, + OUT appname text +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C VOLATILE STRICT; + +CREATE VIEW pg_wait_sampling_history_extended AS + SELECT * FROM pg_wait_sampling_get_history_extended(); + +GRANT SELECT ON pg_wait_sampling_history_extended TO PUBLIC; + +CREATE FUNCTION pg_wait_sampling_get_profile_extended ( + OUT pid int4, + OUT event_type text, + OUT event text, + OUT queryid int8, + OUT role_id int8, + OUT database_id int8, + OUT parallel_leader_pid int4, + OUT backend_type text, + OUT backend_state text, + OUT proc_start timestamptz, + OUT client_addr text, + OUT client_hostname text, + OUT appname text, + OUT count int8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C VOLATILE STRICT; + +CREATE VIEW pg_wait_sampling_profile_extended AS + SELECT * FROM pg_wait_sampling_get_profile_extended(); + +GRANT SELECT ON pg_wait_sampling_profile_extended TO PUBLIC; diff --git a/pg_wait_sampling.c b/pg_wait_sampling.c index e165a6a..fcc4384 100644 --- a/pg_wait_sampling.c +++ b/pg_wait_sampling.c @@ -13,6 +13,7 @@ #include "access/htup_details.h" #include "catalog/pg_type_d.h" +#include "common/ip.h" #include "executor/executor.h" #include "funcapi.h" #include "miscadmin.h" @@ -32,6 +33,7 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/timestamp.h" +#include "utils/varlena.h" #if PG_VERSION_NUM < 150000 #include "postmaster/autovacuum.h" @@ -133,6 +135,10 @@ int pgws_profilePeriod = 10; bool pgws_profilePid = true; int pgws_profileQueries = PGWS_PROFILE_QUERIES_TOP; bool pgws_sampleCpu = true; +static char *pgws_history_dimensions_string = NULL; +static char *pgws_profile_dimensions_string = NULL; +int pgws_history_dimensions; /* bit mask that is derived from GUC */ +int pgws_profile_dimensions; /* bit mask that is derived from GUC */ #define pgws_enabled(level) \ ((pgws_profileQueries == PGWS_PROFILE_QUERIES_ALL) || \ @@ -301,6 +307,109 @@ pgws_cleanup_callback(int code, Datum arg) LockRelease(&queueTag, ExclusiveLock, false); } +/* + * Check tokens of string and fill bitmask accordingly + * Mostly copied from plpgsql_extra_checks_check_hook + */ +static bool +pgws_general_dimensions_check_hook (char **newvalue, void **extra, GucSource source) +{ + char *rawstring; + List *elemlist; + ListCell *l; + int extrachecks = 0; + int *myextra; + + /* Check special cases when we turn all or none dimensions */ + if (pg_strcasecmp(*newvalue, "all") == 0) + extrachecks = PGWS_DIMENSIONS_ALL; + else if (pg_strcasecmp(*newvalue, "none") == 0) + extrachecks = PGWS_DIMENSIONS_NONE; + else + { + /* Need a modifiable copy of string */ + rawstring = pstrdup(*newvalue); + + /* Parse string into list of identifiers */ + if (!SplitIdentifierString(rawstring, ',', &elemlist)) + { + /* syntax error in list */ + GUC_check_errdetail("List syntax is invalid."); + pfree(rawstring); + list_free(elemlist); + return false; + } + + /* Loop over all recieved options */ + foreach(l, elemlist) + { + char *tok = (char *) lfirst(l); + + /* Process all allowed values */ + if (pg_strcasecmp(tok, "role_id") == 0) + extrachecks |= PGWS_DIMENSIONS_ROLE_ID; + else if (pg_strcasecmp(tok, "database_id") == 0) + extrachecks |= PGWS_DIMENSIONS_DB_ID; + else if (pg_strcasecmp(tok, "parallel_leader_pid") == 0) + extrachecks |= PGWS_DIMENSIONS_PARALLEL_LEADER_PID; + else if (pg_strcasecmp(tok, "backend_type") == 0) + extrachecks |= PGWS_DIMENSIONS_BE_TYPE; + else if (pg_strcasecmp(tok, "backend_state") == 0) + extrachecks |= PGWS_DIMENSIONS_BE_STATE; + else if (pg_strcasecmp(tok, "backend_start_time") == 0) + extrachecks |= PGWS_DIMENSIONS_BE_START_TIME; + else if (pg_strcasecmp(tok, "client_addr") == 0) + extrachecks |= PGWS_DIMENSIONS_CLIENT_ADDR; + else if (pg_strcasecmp(tok, "client_hostname") == 0) + extrachecks |= PGWS_DIMENSIONS_CLIENT_HOSTNAME; + else if (pg_strcasecmp(tok, "appname") == 0) + extrachecks |= PGWS_DIMENSIONS_APPNAME; + else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0) + { + GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok); + pfree(rawstring); + list_free(elemlist); + return false; + } + else + { + GUC_check_errdetail("Unrecognized key word: \"%s\".", tok); + pfree(rawstring); + list_free(elemlist); + return false; + } + } + + pfree(rawstring); + list_free(elemlist); + } +#if PG_VERSION_NUM >= 160000 + myextra = (int *) guc_malloc(LOG, sizeof(int)); +#else + myextra = (int *) malloc(sizeof(int)); +#endif + if (!myextra) + return false; + *myextra = extrachecks; + *extra = myextra; + + return true; +} + +/* Assign actual value to dimension bitmask */ +static void +pgws_history_dimensions_assign_hook (const char *newvalue, void *extra) +{ + pgws_history_dimensions = *((int *) extra); +} + +/* Assign actual value to dimension bitmask */ +static void +pgws_profile_dimensions_assign_hook (const char *newvalue, void *extra) +{ + pgws_profile_dimensions = *((int *) extra); +} + /* * Module load callback */ @@ -421,6 +530,28 @@ _PG_init(void) NULL, NULL); + DefineCustomStringVariable("pg_wait_sampling.history_dimensions", + "Sets sampling dimensions for history", + NULL, + &pgws_history_dimensions_string, + "none", + PGC_SIGHUP, + GUC_LIST_INPUT, + pgws_general_dimensions_check_hook, + pgws_history_dimensions_assign_hook, + NULL); + + DefineCustomStringVariable("pg_wait_sampling.profile_dimensions", + "Sets sampling dimensions for profile", + NULL, + &pgws_profile_dimensions_string, + "none", + PGC_SIGHUP, + GUC_LIST_INPUT, + pgws_general_dimensions_check_hook, + pgws_profile_dimensions_assign_hook, + NULL); + #if PG_VERSION_NUM >= 150000 MarkGUCPrefixReserved("pg_wait_sampling"); #endif @@ -609,6 +740,332 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) } } +static Datum +GetBackendState(BackendState state, bool *is_null) +{ + switch (state) + { +#if PG_VERSION_NUM >= 180000 + case STATE_STARTING: + return CStringGetTextDatum("starting"); +#endif + case STATE_IDLE: + return CStringGetTextDatum("idle"); + case STATE_RUNNING: + return CStringGetTextDatum("active"); + case STATE_IDLEINTRANSACTION: + return CStringGetTextDatum("idle in transaction"); + case STATE_FASTPATH: + return CStringGetTextDatum("fastpath function call"); + case STATE_IDLEINTRANSACTION_ABORTED: + return CStringGetTextDatum("idle in transaction (aborted)"); + case STATE_DISABLED: + return CStringGetTextDatum("disabled"); + case STATE_UNDEFINED: + *is_null = true; + } + return (Datum) 0; +} + +/* Copied from pg_stat_get_backend_client_addr */ +static Datum +get_backend_client_addr(SockAddr client_addr, bool *is_null) +{ + char remote_host[NI_MAXHOST]; + int ret; + + /* A zeroed client addr means we don't know */ +#if PG_VERSION_NUM >= 180000 + if (pg_memory_is_all_zeros(&client_addr, + sizeof(client_addr))) +#else + SockAddr zero_clientaddr; + + memset(&zero_clientaddr, 0, sizeof(zero_clientaddr)); + if (memcmp(&client_addr, &zero_clientaddr, + sizeof(zero_clientaddr)) == 0) +#endif + { + *is_null = true; + return (Datum) 0; + } + + switch (client_addr.addr.ss_family) + { + case AF_INET: + case AF_INET6: + break; + default: + *is_null = true; + return (Datum) 0; + } + + remote_host[0] = '\0'; + ret = pg_getnameinfo_all(&client_addr.addr, + client_addr.salen, + remote_host, sizeof(remote_host), + NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret != 0) + { + *is_null = true; + return (Datum) 0; + } + + clean_ipv6_addr(client_addr.addr.ss_family, remote_host); + + return (DirectFunctionCall1(inet_in, CStringGetDatum(remote_host))); +} + +/* + * Needed for PostgreSQL 16 and earlier since there is no good way to get + * PgBackendStatus when having only PGPROC structure. + * + * pgstat_fetch_stat_beentry (13-15) works with indices of localBackendStatusTable + * pgstat_get_beentry_by_backend_id (16) works with "backend_ids", but we still + * cannot get them without looking into LocalPgBackendStatus, so work with indices + * + * This function is very inefficient + * + * Maybe we should just iterate over localBackendStatusTable and somehow get + * PGPROC entries from there but it is up for discussion + */ +PgBackendStatus * +get_beentry_by_procpid(int pid) +{ + int backend_num = pgstat_fetch_stat_numbackends(), cur_be_idx; + + for (cur_be_idx = 1; cur_be_idx <= backend_num; cur_be_idx++) + { + LocalPgBackendStatus *local_beentry; + +#if PG_VERSION_NUM >= 160000 + local_beentry = pgstat_get_local_beentry_by_index(cur_be_idx); +#else + /* Here beid is just index in localBackendStatusTable */ + local_beentry = pgstat_fetch_stat_local_beentry(cur_be_idx); +#endif + if (local_beentry->backendStatus.st_procpid == pid) + return &local_beentry->backendStatus; + } + return NULL; +} + +PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current_extended); +Datum +pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + WaitCurrentContext *params; + + check_shmem(); + + /* Initialization, done only on the first call */ + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + params = (WaitCurrentContext *) palloc0(sizeof(WaitCurrentContext)); + params->ts = GetCurrentTimestamp(); + + funcctx->user_fctx = params; + /* Setup tuple desc */ + tupdesc = CreateTemplateTupleDesc(13); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "role_id", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "database_id", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "parallel_leader_pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "backend_type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_state", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "proc_start", + TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "client_addr", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_hostname", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "appname", + TEXTOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + LWLockAcquire(ProcArrayLock, LW_SHARED); + + if (!PG_ARGISNULL(0)) + { + /* pg_wait_sampling_get_current_extended(pid int4) function */ + HistoryItem *item; + PGPROC *proc; + PgBackendStatus *bestatus; + + proc = search_proc(PG_GETARG_UINT32(0)); +#if PG_VERSION_NUM >= 170000 + bestatus = pgstat_get_beentry_by_proc_number(GetNumberFromPGProc(proc)); +#else + bestatus = get_beentry_by_procpid(proc->pid); +#endif + params->items = (HistoryItem *) palloc0(sizeof(HistoryItem)); + item = ¶ms->items[0]; + /* Show all fields without looking at GUC variables */ + item->pid = proc->pid; + item->wait_event_info = proc->wait_event_info; + item->queryId = pgws_proc_queryids[proc - ProcGlobal->allProcs]; + item->role_id = proc->roleId; + item->database_id = proc->databaseId; + item->parallel_leader_pid = (proc->lockGroupLeader ? + proc->lockGroupLeader->pid : + 0); + if (bestatus) + { + item->backend_type = bestatus->st_backendType; + item->backend_state = bestatus->st_state; + item->proc_start = bestatus->st_proc_start_timestamp; + item->client_addr = bestatus->st_clientaddr; + strcpy(item->client_hostname, bestatus->st_clienthostname); + strcpy(item->appname, bestatus->st_appname); + } + funcctx->max_calls = 1; + } + else + { + /* pg_wait_sampling_current view */ + int procCount = ProcGlobal->allProcCount, + i, + j = 0; + + params->items = (HistoryItem *) palloc0(sizeof(HistoryItem) * procCount); + for (i = 0; i < procCount; i++) + { + PGPROC *proc = &ProcGlobal->allProcs[i]; +#if PG_VERSION_NUM >= 170000 + PgBackendStatus *bestatus = pgstat_get_beentry_by_proc_number(GetNumberFromPGProc(proc)); +#else + PgBackendStatus *bestatus = get_beentry_by_procpid(proc->pid); +#endif + + if (!pgws_should_sample_proc(proc, + ¶ms->items[j].pid, + ¶ms->items[j].wait_event_info)) + continue; + + /* Show all fields without looking at GUC variables */ + params->items[j].pid = proc->pid; + params->items[j].wait_event_info = proc->wait_event_info; + params->items[j].queryId = pgws_proc_queryids[i]; + params->items[j].role_id = proc->roleId; + params->items[j].database_id = proc->databaseId; + params->items[j].parallel_leader_pid = (proc->lockGroupLeader ? + proc->lockGroupLeader->pid : + 0); + if (bestatus) + { + params->items[j].backend_type = bestatus->st_backendType; + params->items[j].backend_state = bestatus->st_state; + params->items[j].proc_start = bestatus->st_proc_start_timestamp; + params->items[j].client_addr = bestatus->st_clientaddr; + strcpy(params->items[j].client_hostname, bestatus->st_clienthostname); + strcpy(params->items[j].appname, bestatus->st_appname); + } + j++; + } + funcctx->max_calls = j; + } + + LWLockRelease(ProcArrayLock); +#if PG_VERSION_NUM >= 140000 + pgstat_clear_backend_activity_snapshot(); +#else + pgstat_clear_snapshot(); +#endif + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + params = (WaitCurrentContext *) funcctx->user_fctx; + + if (funcctx->call_cntr < funcctx->max_calls) + { + HeapTuple tuple; + Datum values[13]; + bool nulls[13]; + const char *event_type, + *event, + *backend_type; + Datum backend_state, proc_start, client_addr; + bool is_null_be_state = false, + is_null_client_addr = false; + HistoryItem *item; + + item = ¶ms->items[funcctx->call_cntr]; + + /* Make and return next tuple to caller */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls)); + + event_type = pgstat_get_wait_event_type(item->wait_event_info); + event = pgstat_get_wait_event(item->wait_event_info); + backend_type = GetBackendTypeDesc(item->backend_type); + backend_state = GetBackendState(item->backend_state, &is_null_be_state); + proc_start = TimestampTzGetDatum(item->proc_start); + client_addr = get_backend_client_addr(item->client_addr, &is_null_client_addr); + + values[0] = Int32GetDatum(item->pid); + if (event_type) + values[1] = PointerGetDatum(cstring_to_text(event_type)); + else + nulls[1] = true; + if (event) + values[2] = PointerGetDatum(cstring_to_text(event)); + else + nulls[2] = true; + values[3] = UInt64GetDatum(item->queryId); + values[4] = ObjectIdGetDatum(item->role_id); + values[5] = ObjectIdGetDatum(item->database_id); + values[6] = Int32GetDatum(item->parallel_leader_pid); + if (backend_type) + values[7] = PointerGetDatum(cstring_to_text(backend_type)); + else + nulls[7] = true; + if (!is_null_be_state) + values[8] = backend_state; + else + nulls[8] = true; + values[9] = proc_start; + if (!is_null_client_addr) + values[10] = client_addr; + else + nulls[10] = true; + values[11] = PointerGetDatum(cstring_to_text(item->client_hostname)); + values[12] = PointerGetDatum(cstring_to_text(item->appname)); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + else + { + /* nothing left */ + SRF_RETURN_DONE(funcctx); + } +} + typedef struct { Size count; @@ -806,6 +1263,161 @@ pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) } } +PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile_extended); +Datum +pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) +{ + Profile *profile; + FuncCallContext *funcctx; + + check_shmem(); + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* Receive profile from shmq */ + profile = (Profile *) palloc0(sizeof(Profile)); + profile->items = (ProfileItem *) receive_array(PROFILE_REQUEST, + sizeof(ProfileItem), &profile->count); + + funcctx->user_fctx = profile; + funcctx->max_calls = profile->count; + + /* Make tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(14); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "role_id", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "database_id", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "parallel_leader_pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "backend_type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_state", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "proc_start", + TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "client_addr", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_hostname", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "appname", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 14, "count", + INT8OID, -1, 0); + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + profile = (Profile *) funcctx->user_fctx; + + if (funcctx->call_cntr < funcctx->max_calls) + { + /* for each row */ + Datum values[14]; + bool nulls[14]; + HeapTuple tuple; + ProfileItem *item; + const char *event_type, + *event, + *backend_type; + Datum backend_state, proc_start, client_addr; + bool is_null_be_state = false, + is_null_client_addr = false; + + item = &profile->items[funcctx->call_cntr]; + + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls)); + + /* Make and return next tuple to caller */ + event_type = pgstat_get_wait_event_type(item->wait_event_info); + event = pgstat_get_wait_event(item->wait_event_info); + backend_type = GetBackendTypeDesc(item->backend_type); + backend_state = GetBackendState(item->backend_state, &is_null_be_state); + proc_start = TimestampTzGetDatum(item->proc_start); + client_addr = get_backend_client_addr(item->client_addr, &is_null_client_addr); + + values[0] = Int32GetDatum(item->pid); + if (event_type) + values[1] = PointerGetDatum(cstring_to_text(event_type)); + else + nulls[1] = true; + if (event) + values[2] = PointerGetDatum(cstring_to_text(event)); + else + nulls[2] = true; + if (pgws_profileQueries) + values[3] = UInt64GetDatum(item->queryId); + else + values[3] = (Datum) 0; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_ROLE_ID) + values[4] = ObjectIdGetDatum(item->role_id); + else + nulls[4] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_DB_ID) + values[5] = ObjectIdGetDatum(item->database_id); + else + nulls[5] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + values[6] = Int32GetDatum(item->parallel_leader_pid); + else + nulls[6] = true; + if (backend_type && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE)) + values[7] = PointerGetDatum(cstring_to_text(backend_type)); + else + nulls[7] = true; + if (!is_null_be_state && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE)) + values[8] = backend_state; + else + nulls[8] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) + values[9] = proc_start; + else + nulls[9] = true; + if (!is_null_client_addr && pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) + values[10] = client_addr; + else + nulls[10] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + values[11] = PointerGetDatum(cstring_to_text(item->client_hostname)); + else + nulls[11] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) + values[12] = PointerGetDatum(cstring_to_text(item->appname)); + else + nulls[12] = true; + + values[13] = UInt64GetDatum(item->count); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + else + { + /* nothing left */ + SRF_RETURN_DONE(funcctx); + } +} + PG_FUNCTION_INFO_V1(pg_wait_sampling_reset_profile); Datum pg_wait_sampling_reset_profile(PG_FUNCTION_ARGS) @@ -928,6 +1540,159 @@ pg_wait_sampling_get_history(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history_extended); +Datum +pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) +{ + History *history; + FuncCallContext *funcctx; + + check_shmem(); + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* Receive history from shmq */ + history = (History *) palloc0(sizeof(History)); + history->items = (HistoryItem *) receive_array(HISTORY_REQUEST, + sizeof(HistoryItem), &history->count); + + funcctx->user_fctx = history; + funcctx->max_calls = history->count; + + /* Make tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(14); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sample_ts", + TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "event", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "queryid", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "role_id", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "database_id", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "parallel_leader_pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "backend_state", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "proc_start", + TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_addr", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "client_hostname", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 14, "appname", + TEXTOID, -1, 0); + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + history = (History *) funcctx->user_fctx; + + if (history->index < history->count) + { + HeapTuple tuple; + HistoryItem *item; + Datum values[14]; + bool nulls[14]; + const char *event_type, + *event, + *backend_type; + Datum backend_state, proc_start, client_addr; + bool is_null_be_state = false, + is_null_client_addr = false; + + item = &history->items[history->index]; + + /* Make and return next tuple to caller */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls)); + + event_type = pgstat_get_wait_event_type(item->wait_event_info); + event = pgstat_get_wait_event(item->wait_event_info); + backend_type = GetBackendTypeDesc(item->backend_type); + backend_state = GetBackendState(item->backend_state, &is_null_be_state); + proc_start = TimestampTzGetDatum(item->proc_start); + client_addr = get_backend_client_addr(item->client_addr, &is_null_client_addr); + + values[0] = Int32GetDatum(item->pid); + values[1] = TimestampTzGetDatum(item->ts); + if (event_type) + values[2] = PointerGetDatum(cstring_to_text(event_type)); + else + nulls[2] = true; + if (event) + values[3] = PointerGetDatum(cstring_to_text(event)); + else + nulls[3] = true; + values[4] = UInt64GetDatum(item->queryId); + if (pgws_profile_dimensions & PGWS_DIMENSIONS_ROLE_ID) + values[5] = ObjectIdGetDatum(item->role_id); + else + nulls[5] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_DB_ID) + values[6] = ObjectIdGetDatum(item->database_id); + else + nulls[6] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + values[7] = Int32GetDatum(item->parallel_leader_pid); + else + nulls[7] = true; + if (backend_type && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE)) + values[8] = PointerGetDatum(cstring_to_text(backend_type)); + else + nulls[8] = true; + if (!is_null_be_state && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE)) + values[9] = backend_state; + else + nulls[9] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) + values[10] = proc_start; + else + nulls[10] = true; + if (!is_null_client_addr && pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) + values[11] = client_addr; + else + nulls[11] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + values[12] = PointerGetDatum(cstring_to_text(item->client_hostname)); + else + nulls[12] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) + values[13] = PointerGetDatum(cstring_to_text(item->appname)); + else + nulls[13] = true; + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + + history->index++; + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + else + { + /* nothing left */ + SRF_RETURN_DONE(funcctx); + } + + PG_RETURN_VOID(); +} + /* * planner_hook hook, save queryId for collector */ diff --git a/pg_wait_sampling.control b/pg_wait_sampling.control index 97d9a34..d2d0ffe 100644 --- a/pg_wait_sampling.control +++ b/pg_wait_sampling.control @@ -1,5 +1,5 @@ # pg_wait_sampling extension comment = 'sampling based statistics of wait events' -default_version = '1.1' +default_version = '1.2' module_pathname = '$libdir/pg_wait_sampling' relocatable = true diff --git a/pg_wait_sampling.h b/pg_wait_sampling.h index dab773c..9009fe8 100644 --- a/pg_wait_sampling.h +++ b/pg_wait_sampling.h @@ -15,26 +15,70 @@ #include "storage/lock.h" #include "storage/shm_mq.h" +#if PG_VERSION_NUM >= 140000 +#include "utils/backend_status.h" +#else +#include "pgstat.h" +#endif + #define PG_WAIT_SAMPLING_MAGIC 0xCA94B107 #define COLLECTOR_QUEUE_SIZE (16 * 1024) #define HISTORY_TIME_MULTIPLIER 10 #define PGWS_QUEUE_LOCK 0 #define PGWS_COLLECTOR_LOCK 1 +/* Values for sampling dimensions */ +#define PGWS_DIMENSIONS_NONE 0 + +#define PGWS_DIMENSIONS_ROLE_ID (1 << 1) +#define PGWS_DIMENSIONS_DB_ID (1 << 2) +#define PGWS_DIMENSIONS_PARALLEL_LEADER_PID (1 << 3) +#define PGWS_DIMENSIONS_BE_TYPE (1 << 4) +#define PGWS_DIMENSIONS_BE_STATE (1 << 5) +#define PGWS_DIMENSIONS_BE_START_TIME (1 << 6) +#define PGWS_DIMENSIONS_CLIENT_ADDR (1 << 7) +#define PGWS_DIMENSIONS_CLIENT_HOSTNAME (1 << 8) +#define PGWS_DIMENSIONS_APPNAME (1 << 9) + +#define PGWS_DIMENSIONS_ALL ((int) ~0) +/* ^ all 1 in binary */ + +/* + * Next two structures must match in fields until count/ts so make_profile_hash + * works properly + */ typedef struct { - int pid; - uint32 wait_event_info; - uint64 queryId; - uint64 count; + int pid; + uint32 wait_event_info; + uint64 queryId; + Oid role_id; + Oid database_id; + int parallel_leader_pid; + BackendType backend_type; + BackendState backend_state; + TimestampTz proc_start; + SockAddr client_addr; + char client_hostname[NAMEDATALEN]; + char appname[NAMEDATALEN]; + uint64 count; } ProfileItem; typedef struct { - int pid; - uint32 wait_event_info; - uint64 queryId; - TimestampTz ts; + int pid; + uint32 wait_event_info; + uint64 queryId; + Oid role_id; + Oid database_id; + int parallel_leader_pid; + BackendType backend_type; + BackendState backend_state; + TimestampTz proc_start; + SockAddr client_addr; + char client_hostname[NAMEDATALEN]; + char appname[NAMEDATALEN]; + TimestampTz ts; } HistoryItem; typedef struct @@ -73,6 +117,9 @@ extern shm_mq *pgws_collector_mq; extern uint64 *pgws_proc_queryids; extern void pgws_init_lock_tag(LOCKTAG *tag, uint32 lock); extern bool pgws_should_sample_proc(PGPROC *proc, int *pid_p, uint32 *wait_event_info_p); +extern int pgws_history_dimensions; /* bit mask that is derived from GUC */ +extern int pgws_profile_dimensions; /* bit mask that is derived from GUC */ +extern PgBackendStatus* get_beentry_by_procpid(int pid); /* collector.c */ extern void pgws_register_wait_collector(void); diff --git a/sql/queries.sql b/sql/queries.sql index de44c6d..6658c74 100644 --- a/sql/queries.sql +++ b/sql/queries.sql @@ -9,10 +9,24 @@ WITH t as (SELECT sum(0) FROM pg_wait_sampling_history) WITH t as (SELECT sum(0) FROM pg_wait_sampling_profile) SELECT sum(0) FROM generate_series(1, 2), t; +WITH t as (SELECT sum(0) FROM pg_wait_sampling_current_extended) + SELECT sum(0) FROM generate_series(1, 2), t; + +WITH t as (SELECT sum(0) FROM pg_wait_sampling_history_extended) + SELECT sum(0) FROM generate_series(1, 2), t; + +WITH t as (SELECT sum(0) FROM pg_wait_sampling_profile_extended) + SELECT sum(0) FROM generate_series(1, 2), t; + -- Some dummy checks just to be sure that all our functions work and return something. SELECT count(*) = 1 as test FROM pg_wait_sampling_get_current(pg_backend_pid()); SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_profile(); SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_history(); SELECT pg_wait_sampling_reset_profile(); +SELECT count(*) = 1 as test FROM pg_wait_sampling_get_current_extended(pg_backend_pid()); +SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_profile_extended(); +SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_history_extended(); +SELECT pg_wait_sampling_reset_profile(); + DROP EXTENSION pg_wait_sampling; From 9208eb2cce05a4e60503bed1e0677cf6edb1101e Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Wed, 26 Mar 2025 16:21:17 +0700 Subject: [PATCH 02/11] Update README to include information about new *_extended views Also fix some typos/reword some sentences --- README.md | 135 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 107 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index bbdbd20..f5f68cd 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ Introduction PostgreSQL provides information about current wait event of particular process. However, in order to gather descriptive statistics of server -behavior user have to sample current wait event multiple times. +behavior users have to sample current wait events multiple times. `pg_wait_sampling` is an extension for collecting sampling statistics of wait events. The module must be loaded by adding `pg_wait_sampling` to `shared_preload_libraries` in postgresql.conf, because it requires additional -shared memory and launches background worker. This means that a server restart +shared memory and launches a background worker. This means that a server restart is needed to add or remove the module. When used with `pg_stat_statements` it is recommended to put `pg_stat_statements` @@ -25,17 +25,16 @@ utility statements are not rewritten by the former. When `pg_wait_sampling` is enabled, it collects two kinds of statistics. * History of waits events. It's implemented as in-memory ring buffer where - samples of each process wait events are written with given (configurable) + samples of each process' wait events are written with given (configurable) period. Therefore, for each running process user can see some number of - recent samples depending on history size (configurable). Assuming there is - a client who periodically read this history and dump it somewhere, user - can have continuous history. - * Waits profile. It's implemented as in-memory hash table where count - of samples are accumulated per each process and each wait event - (and each query with `pg_stat_statements`). This hash - table can be reset by user request. Assuming there is a client who - periodically dumps profile and resets it, user can have statistics of - intensivity of wait events among time. + recent samples depending on history size (configurable). Assuming there is + a client who periodically reads this history and dumps it somewhere, user + can have continuous history of wait events. + * Waits profile. It's implemented as in-memory hash table where samples + are accumulated per each wait event and can be divided by process, + query and other dimensions. This hash table can be reset by user request. + Assuming there is a client who periodically dumps profile and resets it, + user can have statistics of wait events over time. In combination with `pg_stat_statements` this extension can also provide per query statistics. @@ -66,10 +65,10 @@ Manual build higher. Before build and install you should ensure following: * PostgreSQL version is 13 or higher. - * You have development package of PostgreSQL installed or you built + * You have development package of PostgreSQL installed or you have built PostgreSQL from source. * Your PATH variable is configured so that `pg_config` command available, or - set PG_CONFIG variable. + PG_CONFIG variable is set. Typical installation procedure may look like this: @@ -98,9 +97,9 @@ Usage `pg_wait_sampling` interacts with user by set of views and functions. `pg_wait_sampling_current` view – information about current wait events for -all processed including background workers. +all processes including background workers. -| Column name | Column type | Description | +| Column name | Column type | Description | | ----------- | ----------- | ----------------------- | | pid | int4 | Id of process | | event_type | text | Name of wait event type | @@ -110,10 +109,33 @@ all processed including background workers. `pg_wait_sampling_get_current(pid int4)` returns the same table for single given process. +`pg_wait_sampling_current_extended` view – information about current wait events for +all processes including background workers. Structure of this view can be changed +between verions. + +| Column name | Column type | Description | +| ------------------- | ----------- | --------------------------- | +| pid | int4 | Id of process | +| event_type | text | Name of wait event type | +| event | text | Name of wait event | +| queryid | int8 | Id of query | +| role_id | int4 | Id of role | +| database_id | int4 | Id of database | +| parallel_leader_pid | int4 | Id of parallel query leader | +| backend_type | text | Name of backend type | +| backend_state | text | Name of backend state | +| proc_start | timestamptz | Timestamp of process start | +| client_addr | text | Client address | +| client_hostname | text | Client hostname | +| appname | text | Application name | + +`pg_wait_sampling_get_current_extended(pid int4)` returns the same table for single given +process. + `pg_wait_sampling_history` view – history of wait events obtained by sampling into in-memory ring buffer. -| Column name | Column type | Description | +| Column name | Column type | Description | | ----------- | ----------- | ----------------------- | | pid | int4 | Id of process | | ts | timestamptz | Sample timestamp | @@ -121,30 +143,74 @@ in-memory ring buffer. | event | text | Name of wait event | | queryid | int8 | Id of query | +`pg_wait_sampling_history_extended` view – history of wait events obtained by +sampling into in-memory ring buffer. Structure of this view can be changed +between verions + +| Column name | Column type | Description | +| ------------------- | ----------- | --------------------------- | +| pid | int4 | Id of process | +| ts | timestamptz | Sample timestamp | +| event_type | text | Name of wait event type | +| event | text | Name of wait event | +| queryid | int8 | Id of query | +| role_id | int4 | Id of role | +| database_id | int4 | Id of database | +| parallel_leader_pid | int4 | Id of parallel query leader | +| backend_type | text | Name of backend type | +| backend_state | text | Name of backend state | +| proc_start | timestamptz | Timestamp of process start | +| client_addr | text | Client address | +| client_hostname | text | Client hostname | +| appname | text | Application name | + `pg_wait_sampling_profile` view – profile of wait events obtained by sampling into in-memory hash table. -| Column name | Column type | Description | +| Column name | Column type | Description | | ----------- | ----------- | ----------------------- | | pid | int4 | Id of process | | event_type | text | Name of wait event type | | event | text | Name of wait event | | queryid | int8 | Id of query | -| count | text | Count of samples | +| count | int8 | Count of samples | + +`pg_wait_sampling_profile_extended` view – history of wait events obtained by +sampling into in-memory ring buffer. Structure of this view can be changed +between verions + +| Column name | Column type | Description | +| ------------------- | ----------- | --------------------------- | +| pid | int4 | Id of process | +| event_type | text | Name of wait event type | +| event | text | Name of wait event | +| queryid | int8 | Id of query | +| role_id | int4 | Id of role | +| database_id | int4 | Id of database | +| parallel_leader_pid | int4 | Id of parallel query leader | +| backend_type | text | Name of backend type | +| backend_state | text | Name of backend state | +| proc_start | timestamptz | Timestamp of process start | +| client_addr | text | Client address | +| client_hostname | text | Client hostname | +| appname | text | Application name | +| count | int8 | Count of samples | `pg_wait_sampling_reset_profile()` function resets the profile. The work of wait event statistics collector worker is controlled by following GUCs. -| Parameter name | Data type | Description | Default value | -|----------------------------------| --------- |---------------------------------------------|--------------:| -| pg_wait_sampling.history_size | int4 | Size of history in-memory ring buffer | 5000 | -| pg_wait_sampling.history_period | int4 | Period for history sampling in milliseconds | 10 | -| pg_wait_sampling.profile_period | int4 | Period for profile sampling in milliseconds | 10 | -| pg_wait_sampling.profile_pid | bool | Whether profile should be per pid | true | -| pg_wait_sampling.profile_queries | enum | Whether profile should be per query | top | -| pg_wait_sampling.sample_cpu | bool | Whether on CPU backends should be sampled | true | +| Parameter name | Data type | Description | Default value | +|-------------------------------------| --------- |---------------------------------------------|--------------:| +| pg_wait_sampling.history_size | int4 | Size of history in-memory ring buffer | 5000 | +| pg_wait_sampling.history_period | int4 | Period for history sampling in milliseconds | 10 | +| pg_wait_sampling.profile_period | int4 | Period for profile sampling in milliseconds | 10 | +| pg_wait_sampling.profile_pid | bool | Whether profile should be per pid | true | +| pg_wait_sampling.profile_queries | enum | Whether profile should be per query | top | +| pg_wait_sampling.sample_cpu | bool | Whether on CPU backends should be sampled | true | +| pg_wait_sampling.history_dimensions | text | Additional columns in extended history view | 'none' | +| pg_wait_sampling.profile_dimensions | text | Additional columns in extended profile view | 'none' | If `pg_wait_sampling.profile_pid` is set to false, sampling profile wouldn't be collected in per-process manner. In this case the value of pid could would @@ -158,6 +224,19 @@ If `pg_wait_sampling.sample_cpu` is set to true then processes that are not waiting on anything are also sampled. The wait event columns for such processes will be NULL. +`pg_wait_sampling.history_dimenstions` and `pg_wait_sampling.profile_dimensions` +determine what additional columns will be sampled in `history/profile_extended` +views. Possible values are `none`, `all`, `role_id`, `database_id`, +`parallel_leader_pid`, `backend_type`, `backend_state`, `backend_start_time`, +`client_addr`, `client_hostname`, `appname` and any combination of column names. +`none` and `all` cannot be used together with any other values and must be used alone. + +> [!WARNING] +> Turning on any of the following columns: `backend_type`, `backend_state`, +> `backend_start_time`, `client_addr`, `client_hostname`, `appname` will reduce +> performance compared to sampling none of those due to the need to look into +> BackendStatusTable. This is especially noticeable with PostgreSQL 13-16 + Values of these GUC variables can be changed only in config file or with ALTER SYSTEM. Then you need to reload server's configuration (such as with pg_reload_conf function) for changes to take effect. @@ -170,7 +249,7 @@ Contribution ------------ Please, notice, that `pg_wait_sampling` is still under development and while -it's stable and tested, it may contains some bugs. Don't hesitate to raise +it's stable and tested, it may contain some bugs. Don't hesitate to raise [issues at github](https://github.com/postgrespro/pg_wait_sampling/issues) with your bug reports. From f0ee939d959f441fb1e18e654dde8ecdc924c2e7 Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Mon, 2 Jun 2025 15:21:42 +0300 Subject: [PATCH 03/11] Fixes after review --- collector.c | 206 ++++++++++++--------- pg_wait_sampling--1.1--1.2.sql | 17 ++ pg_wait_sampling.c | 314 +++++++++++++++++---------------- pg_wait_sampling.h | 39 ++-- 4 files changed, 322 insertions(+), 254 deletions(-) diff --git a/collector.c b/collector.c index e073062..8b1f5a1 100644 --- a/collector.c +++ b/collector.c @@ -149,6 +149,111 @@ get_next_observation(History *observations) return result; } +static void +fill_dimensions(SamplingDimensions *dimensions, PGPROC *proc, + int pid, uint32 wait_event_info, uint64 queryId, + int dimensions_mask) +{ + Oid role_id = proc->roleId; + Oid database_id = proc->databaseId; + PGPROC *lockGroupLeader = proc->lockGroupLeader; + bool is_regular_backend = proc->isRegularBackend; + + dimensions->pid = pid; + + dimensions->wait_event_info = wait_event_info; + + if (pgws_profileQueries) + dimensions->queryId = queryId; + + /* Copy everything we need from PGPROC */ + if (dimensions_mask & PGWS_DIMENSIONS_ROLE_ID) + dimensions->role_id = role_id; + + if (dimensions_mask & PGWS_DIMENSIONS_DB_ID) + dimensions->database_id = database_id; + + if (dimensions_mask & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + dimensions->parallel_leader_pid = (lockGroupLeader ? + lockGroupLeader->pid : + 0); + + if (dimensions_mask & PGWS_DIMENSIONS_IS_REGULAR_BE) + dimensions->is_regular_backend = is_regular_backend; + + /* Look into BackendStatus only if necessary */ + if (check_bestatus_dimensions(dimensions_mask)) + { +#if PG_VERSION_NUM >= 170000 + PgBackendStatus *bestatus = pgstat_get_beentry_by_proc_number(GetNumberFromPGProc(proc)); +#else + PgBackendStatus *bestatus = get_beentry_by_procpid(proc->pid); +#endif + /* Copy everything we need from BackendStatus */ + if (bestatus) + { + if (dimensions_mask & PGWS_DIMENSIONS_BE_TYPE) + dimensions->backend_type = bestatus->st_backendType; + + if (dimensions_mask & PGWS_DIMENSIONS_BE_STATE) + dimensions->backend_state = bestatus->st_state; + + if (dimensions_mask & PGWS_DIMENSIONS_BE_START_TIME) + dimensions->proc_start = bestatus->st_proc_start_timestamp; + + if (dimensions_mask & PGWS_DIMENSIONS_CLIENT_ADDR) + dimensions->client_addr = bestatus->st_clientaddr; + + if (dimensions_mask & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + strcpy(dimensions->client_hostname, bestatus->st_clienthostname); + + if (dimensions_mask & PGWS_DIMENSIONS_APPNAME) + strcpy(dimensions->appname, bestatus->st_appname); + } + } +} + +static void +copy_dimensions (SamplingDimensions *dst, SamplingDimensions *src, + int dst_dimensions_mask) +{ + dst->pid = src->pid; + + dst->wait_event_info = src->wait_event_info; + + dst->queryId = src->queryId; + + if (dst_dimensions_mask & PGWS_DIMENSIONS_ROLE_ID) + dst->role_id = src->role_id; + + if (dst_dimensions_mask & PGWS_DIMENSIONS_DB_ID) + dst->database_id = src->database_id; + + if (dst_dimensions_mask & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + dst->parallel_leader_pid = src->parallel_leader_pid; + + if (dst_dimensions_mask & PGWS_DIMENSIONS_IS_REGULAR_BE) + dst->is_regular_backend = src->is_regular_backend; + + if (dst_dimensions_mask & PGWS_DIMENSIONS_BE_TYPE) + dst->backend_type = src->backend_type; + + if (dst_dimensions_mask & PGWS_DIMENSIONS_BE_STATE) + dst->backend_state = src->backend_state; + + if (dst_dimensions_mask & PGWS_DIMENSIONS_BE_START_TIME) + dst->proc_start = src->proc_start; + + if (dst_dimensions_mask & PGWS_DIMENSIONS_CLIENT_ADDR) + dst->client_addr = src->client_addr; + + if (dst_dimensions_mask & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + strcpy(dst->client_hostname, src->client_hostname); + + if (dst_dimensions_mask & PGWS_DIMENSIONS_APPNAME) + strcpy(dst->appname, src->appname); +} + /* * Read current waits from backends and write them to history array * and/or profile hash. @@ -176,92 +281,34 @@ probe_waits(History *observations, HTAB *profile_hash, PGPROC *proc = &ProcGlobal->allProcs[i]; int pid; uint32 wait_event_info; + SamplingDimensions common_dimensions; + int dimensions_mask_common = pgws_history_dimensions | + pgws_profile_dimensions; /* Check if we need to sample this process */ if (!pgws_should_sample_proc(proc, &pid, &wait_event_info)) continue; - /* We zero whole HistoryItem to avoid doing it field-by-field */ + /* + * We zero items and dimensions with memset + * to avoid doing it field-by-field + */ memset(&item_history, 0, sizeof(HistoryItem)); memset(&item_profile, 0, sizeof(ProfileItem)); + memset(&common_dimensions, 0, sizeof(SamplingDimensions)); - item_history.pid = pid; - item_profile.pid = pid; + fill_dimensions(&common_dimensions, proc, pid, wait_event_info, + pgws_proc_queryids[i], dimensions_mask_common); - item_history.wait_event_info = wait_event_info; - item_profile.wait_event_info = wait_event_info; - - if (pgws_profileQueries) - { - item_history.queryId = pgws_proc_queryids[i]; - item_profile.queryId = pgws_proc_queryids[i]; - } + copy_dimensions(&item_history.dimensions, + &common_dimensions, + pgws_history_dimensions); + copy_dimensions(&item_history.dimensions, + &common_dimensions, + pgws_profile_dimensions); item_history.ts = ts; - /* Copy everything we need from PGPROC */ - if (pgws_history_dimensions & PGWS_DIMENSIONS_ROLE_ID) - item_history.role_id = proc->roleId; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_ROLE_ID) - item_profile.role_id = proc->roleId; - - if (pgws_history_dimensions & PGWS_DIMENSIONS_DB_ID) - item_history.database_id = proc->databaseId; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_DB_ID) - item_profile.database_id = proc->databaseId; - - if (pgws_history_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) - item_history.parallel_leader_pid = (proc->lockGroupLeader ? - proc->lockGroupLeader->pid : - 0); - if (pgws_profile_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) - item_profile.parallel_leader_pid = (proc->lockGroupLeader ? - proc->lockGroupLeader->pid : - 0); - /* Look into BackendStatus only if necessary */ - if (check_bestatus_dimensions(pgws_history_dimensions) || - check_bestatus_dimensions(pgws_profile_dimensions)) - { -#if PG_VERSION_NUM >= 170000 - PgBackendStatus *bestatus = pgstat_get_beentry_by_proc_number(GetNumberFromPGProc(proc)); -#else - PgBackendStatus *bestatus = get_beentry_by_procpid(proc->pid); -#endif - /* Copy everything we need from BackendStatus */ - if (bestatus) - { - if (pgws_history_dimensions & PGWS_DIMENSIONS_BE_TYPE) - item_history.backend_type = bestatus->st_backendType; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE) - item_profile.backend_type = bestatus->st_backendType; - - if (pgws_history_dimensions & PGWS_DIMENSIONS_BE_STATE) - item_history.backend_state = bestatus->st_state; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE) - item_profile.backend_state = bestatus->st_state; - - if (pgws_history_dimensions & PGWS_DIMENSIONS_BE_START_TIME) - item_history.proc_start = bestatus->st_proc_start_timestamp; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) - item_profile.proc_start = bestatus->st_proc_start_timestamp; - - if (pgws_history_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) - item_history.client_addr = bestatus->st_clientaddr; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) - item_profile.client_addr = bestatus->st_clientaddr; - - if (pgws_history_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) - strcpy(item_history.client_hostname, bestatus->st_clienthostname); - if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) - strcpy(item_profile.client_hostname, bestatus->st_clienthostname); - - if (pgws_history_dimensions & PGWS_DIMENSIONS_APPNAME) - strcpy(item_history.appname, bestatus->st_appname); - if (pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) - strcpy(item_profile.appname, bestatus->st_appname); - } - } - /* Write to the history if needed */ if (write_history) { @@ -276,9 +323,10 @@ probe_waits(History *observations, HTAB *profile_hash, bool found; if (!profile_pid) - item_profile.pid = 0; + item_profile.dimensions.pid = 0; - profileItem = (ProfileItem *) hash_search(profile_hash, &item_profile, HASH_ENTER, &found); + profileItem = (ProfileItem *) hash_search(profile_hash, &item_profile, + HASH_ENTER, &found); if (found) profileItem->count++; else @@ -379,11 +427,11 @@ make_profile_hash() HASHCTL hash_ctl; /* - * Since adding additional dimensions we include everyting except count - * into hashtable key. This is fine for cases when some fields are 0 since + * Since adding additional dimensions we use SamplingDimensions as + * hashtable key. This is fine for cases when some fields are 0 since * it doesn't impede our ability to search the hash table for entries */ - hash_ctl.keysize = offsetof(ProfileItem, count); + hash_ctl.keysize = sizeof(SamplingDimensions); hash_ctl.entrysize = sizeof(ProfileItem); return hash_create("Waits profile hash", 1024, &hash_ctl, diff --git a/pg_wait_sampling--1.1--1.2.sql b/pg_wait_sampling--1.1--1.2.sql index df95826..29def16 100644 --- a/pg_wait_sampling--1.1--1.2.sql +++ b/pg_wait_sampling--1.1--1.2.sql @@ -3,6 +3,13 @@ -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pg_wait_sampling UPDATE TO 1.2" to load this file. \quit +DROP FUNCTION pg_wait_sampling_get_profile ( + OUT pid int4, + OUT event_type text, + OUT event text, + OUT count bigint +) CASCADE; + CREATE FUNCTION pg_wait_sampling_get_current_extended ( pid int4, OUT pid int4, @@ -12,6 +19,7 @@ CREATE FUNCTION pg_wait_sampling_get_current_extended ( OUT role_id int8, OUT database_id int8, OUT parallel_leader_pid int4, + OUT is_regular_backend bool, OUT backend_type text, OUT backend_state text, OUT proc_start timestamptz, @@ -37,6 +45,7 @@ CREATE FUNCTION pg_wait_sampling_get_history_extended ( OUT role_id int8, OUT database_id int8, OUT parallel_leader_pid int4, + OUT is_regular_backend bool, OUT backend_type text, OUT backend_state text, OUT proc_start timestamptz, @@ -61,6 +70,7 @@ CREATE FUNCTION pg_wait_sampling_get_profile_extended ( OUT role_id int8, OUT database_id int8, OUT parallel_leader_pid int4, + OUT is_regular_backend bool, OUT backend_type text, OUT backend_state text, OUT proc_start timestamptz, @@ -77,3 +87,10 @@ CREATE VIEW pg_wait_sampling_profile_extended AS SELECT * FROM pg_wait_sampling_get_profile_extended(); GRANT SELECT ON pg_wait_sampling_profile_extended TO PUBLIC; + +CREATE VIEW pg_wait_sampling_profile AS + SELECT pid, event_type, event, queryid, SUM(count) FROM pg_wait_sampling_profile_extended + GROUP BY pid, event_type, event, queryid; + +GRANT SELECT ON pg_wait_sampling_profile TO PUBLIC; + diff --git a/pg_wait_sampling.c b/pg_wait_sampling.c index fcc4384..d0ebd10 100644 --- a/pg_wait_sampling.c +++ b/pg_wait_sampling.c @@ -352,6 +352,8 @@ pgws_general_dimensions_check_hook (char **newvalue, void **extra, GucSource sou extrachecks |= PGWS_DIMENSIONS_DB_ID; else if (pg_strcasecmp(tok, "parallel_leader_pid") == 0) extrachecks |= PGWS_DIMENSIONS_PARALLEL_LEADER_PID; + else if (pg_strcasecmp(tok, "is_regular_backend") == 0) + extrachecks |= PGWS_DIMENSIONS_IS_REGULAR_BE; else if (pg_strcasecmp(tok, "backend_type") == 0) extrachecks |= PGWS_DIMENSIONS_BE_TYPE; else if (pg_strcasecmp(tok, "backend_state") == 0) @@ -662,9 +664,9 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) proc = search_proc(PG_GETARG_UINT32(0)); params->items = (HistoryItem *) palloc0(sizeof(HistoryItem)); item = ¶ms->items[0]; - item->pid = proc->pid; - item->wait_event_info = proc->wait_event_info; - item->queryId = pgws_proc_queryids[proc - ProcGlobal->allProcs]; + item->dimensions.pid = proc->pid; + item->dimensions.wait_event_info = proc->wait_event_info; + item->dimensions.queryId = pgws_proc_queryids[proc - ProcGlobal->allProcs]; funcctx->max_calls = 1; } else @@ -680,13 +682,13 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) PGPROC *proc = &ProcGlobal->allProcs[i]; if (!pgws_should_sample_proc(proc, - ¶ms->items[j].pid, - ¶ms->items[j].wait_event_info)) + ¶ms->items[j].dimensions.pid, + ¶ms->items[j].dimensions.wait_event_info)) continue; - params->items[j].pid = proc->pid; - params->items[j].wait_event_info = proc->wait_event_info; - params->items[j].queryId = pgws_proc_queryids[i]; + params->items[j].dimensions.pid = proc->pid; + params->items[j].dimensions.wait_event_info = proc->wait_event_info; + params->items[j].dimensions.queryId = pgws_proc_queryids[i]; j++; } funcctx->max_calls = j; @@ -716,9 +718,9 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - event_type = pgstat_get_wait_event_type(item->wait_event_info); - event = pgstat_get_wait_event(item->wait_event_info); - values[0] = Int32GetDatum(item->pid); + event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); + event = pgstat_get_wait_event(item->dimensions.wait_event_info); + values[0] = Int32GetDatum(item->dimensions.pid); if (event_type) values[1] = PointerGetDatum(cstring_to_text(event_type)); else @@ -728,7 +730,7 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) else nulls[2] = true; - values[3] = UInt64GetDatum(item->queryId); + values[3] = UInt64GetDatum(item->dimensions.queryId); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); @@ -874,7 +876,7 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) funcctx->user_fctx = params; /* Setup tuple desc */ - tupdesc = CreateTemplateTupleDesc(13); + tupdesc = CreateTemplateTupleDesc(14); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", @@ -889,17 +891,19 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) INT8OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 7, "parallel_leader_pid", INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 8, "backend_type", + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "is_regular_backend", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_type", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_state", + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "backend_state", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 10, "proc_start", + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "proc_start", TIMESTAMPTZOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 11, "client_addr", + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_addr", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_hostname", + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "client_hostname", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 13, "appname", + TupleDescInitEntry(tupdesc, (AttrNumber) 14, "appname", TEXTOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); @@ -914,31 +918,15 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) PgBackendStatus *bestatus; proc = search_proc(PG_GETARG_UINT32(0)); -#if PG_VERSION_NUM >= 170000 - bestatus = pgstat_get_beentry_by_proc_number(GetNumberFromPGProc(proc)); -#else - bestatus = get_beentry_by_procpid(proc->pid); -#endif + params->items = (HistoryItem *) palloc0(sizeof(HistoryItem)); item = ¶ms->items[0]; - /* Show all fields without looking at GUC variables */ - item->pid = proc->pid; - item->wait_event_info = proc->wait_event_info; - item->queryId = pgws_proc_queryids[proc - ProcGlobal->allProcs]; - item->role_id = proc->roleId; - item->database_id = proc->databaseId; - item->parallel_leader_pid = (proc->lockGroupLeader ? - proc->lockGroupLeader->pid : - 0); - if (bestatus) - { - item->backend_type = bestatus->st_backendType; - item->backend_state = bestatus->st_state; - item->proc_start = bestatus->st_proc_start_timestamp; - item->client_addr = bestatus->st_clientaddr; - strcpy(item->client_hostname, bestatus->st_clienthostname); - strcpy(item->appname, bestatus->st_appname); - } + + fill_dimensions(&item->dimensions, proc, proc->pid, + proc->wait_event_info, + pgws_proc_queryids[proc - ProcGlobal->allProcs], + PGWS_DIMENSIONS_ALL); + funcctx->max_calls = 1; } else @@ -952,35 +940,36 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) for (i = 0; i < procCount; i++) { PGPROC *proc = &ProcGlobal->allProcs[i]; -#if PG_VERSION_NUM >= 170000 - PgBackendStatus *bestatus = pgstat_get_beentry_by_proc_number(GetNumberFromPGProc(proc)); -#else - PgBackendStatus *bestatus = get_beentry_by_procpid(proc->pid); -#endif - if (!pgws_should_sample_proc(proc, - ¶ms->items[j].pid, - ¶ms->items[j].wait_event_info)) + if (!pgws_should_sample_proc(&proc, + ¶ms->items[j].dimensions.pid, + ¶ms->items[j].dimensions.wait_event_info)) continue; - /* Show all fields without looking at GUC variables */ - params->items[j].pid = proc->pid; - params->items[j].wait_event_info = proc->wait_event_info; - params->items[j].queryId = pgws_proc_queryids[i]; - params->items[j].role_id = proc->roleId; - params->items[j].database_id = proc->databaseId; - params->items[j].parallel_leader_pid = (proc->lockGroupLeader ? - proc->lockGroupLeader->pid : - 0); - if (bestatus) - { - params->items[j].backend_type = bestatus->st_backendType; - params->items[j].backend_state = bestatus->st_state; - params->items[j].proc_start = bestatus->st_proc_start_timestamp; - params->items[j].client_addr = bestatus->st_clientaddr; - strcpy(params->items[j].client_hostname, bestatus->st_clienthostname); - strcpy(params->items[j].appname, bestatus->st_appname); - } + fill_dimensions(¶ms->items[j]->dimensions, proc, proc->pid, + proc->wait_event_info, + pgws_proc_queryids[proc - ProcGlobal->allProcs], + PGWS_DIMENSIONS_ALL); + +// /* Show all fields without looking at GUC variables */ +// params->items[j].dimensions.pid = proc.pid; +// params->items[j].dimensions.wait_event_info = proc.wait_event_info; +// params->items[j].dimensions.queryId = pgws_proc_queryids[i]; +// params->items[j].dimensions.role_id = proc.roleId; +// params->items[j].dimensions.database_id = proc.databaseId; +// params->items[j].dimensions.parallel_leader_pid = (proc.lockGroupLeader ? +// proc.lockGroupLeader->pid : +// 0); +// params->items[j].dimensions.is_regular_backend = proc.isRegularBackend; +// if (bestatus) +// { +// params->items[j].dimensions.backend_type = bestatus->st_backendType; +// params->items[j].dimensions.backend_state = bestatus->st_state; +// params->items[j].dimensions.proc_start = bestatus->st_proc_start_timestamp; +// params->items[j].dimensions.client_addr = bestatus->st_clientaddr; +// strcpy(params->items[j].dimensions.client_hostname, bestatus->st_clienthostname); +// strcpy(params->items[j].dimensions.appname, bestatus->st_appname); +// } j++; } funcctx->max_calls = j; @@ -1003,8 +992,8 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) if (funcctx->call_cntr < funcctx->max_calls) { HeapTuple tuple; - Datum values[13]; - bool nulls[13]; + Datum values[14]; + bool nulls[14]; const char *event_type, *event, *backend_type; @@ -1019,14 +1008,14 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - event_type = pgstat_get_wait_event_type(item->wait_event_info); - event = pgstat_get_wait_event(item->wait_event_info); - backend_type = GetBackendTypeDesc(item->backend_type); - backend_state = GetBackendState(item->backend_state, &is_null_be_state); - proc_start = TimestampTzGetDatum(item->proc_start); - client_addr = get_backend_client_addr(item->client_addr, &is_null_client_addr); + event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); + event = pgstat_get_wait_event(item->dimensions.wait_event_info); + backend_type = GetBackendTypeDesc(item->dimensions.backend_type); + backend_state = GetBackendState(item->dimensions.backend_state, &is_null_be_state); + proc_start = TimestampTzGetDatum(item->dimensions.proc_start); + client_addr = get_backend_client_addr(item->dimensions.client_addr, &is_null_client_addr); - values[0] = Int32GetDatum(item->pid); + values[0] = Int32GetDatum(item->dimensions.pid); if (event_type) values[1] = PointerGetDatum(cstring_to_text(event_type)); else @@ -1035,10 +1024,11 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) values[2] = PointerGetDatum(cstring_to_text(event)); else nulls[2] = true; - values[3] = UInt64GetDatum(item->queryId); - values[4] = ObjectIdGetDatum(item->role_id); - values[5] = ObjectIdGetDatum(item->database_id); - values[6] = Int32GetDatum(item->parallel_leader_pid); + values[3] = UInt64GetDatum(item->dimensions.queryId); + values[4] = ObjectIdGetDatum(item->dimensions.role_id); + values[5] = ObjectIdGetDatum(item->dimensions.database_id); + values[6] = Int32GetDatum(item->dimensions.parallel_leader_pid); + values[7] = BoolGetDatum(item->dimensions.is_regular_backend); if (backend_type) values[7] = PointerGetDatum(cstring_to_text(backend_type)); else @@ -1052,8 +1042,8 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) values[10] = client_addr; else nulls[10] = true; - values[11] = PointerGetDatum(cstring_to_text(item->client_hostname)); - values[12] = PointerGetDatum(cstring_to_text(item->appname)); + values[11] = PointerGetDatum(cstring_to_text(item->dimensions.client_hostname)); + values[12] = PointerGetDatum(cstring_to_text(item->dimensions.appname)); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); @@ -1233,9 +1223,9 @@ pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) MemSet(nulls, 0, sizeof(nulls)); /* Make and return next tuple to caller */ - event_type = pgstat_get_wait_event_type(item->wait_event_info); - event = pgstat_get_wait_event(item->wait_event_info); - values[0] = Int32GetDatum(item->pid); + event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); + event = pgstat_get_wait_event(item->dimensions.wait_event_info); + values[0] = Int32GetDatum(item->dimensions.pid); if (event_type) values[1] = PointerGetDatum(cstring_to_text(event_type)); else @@ -1246,7 +1236,7 @@ pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) nulls[2] = true; if (pgws_profileQueries) - values[3] = UInt64GetDatum(item->queryId); + values[3] = UInt64GetDatum(item->dimensions.queryId); else values[3] = (Datum) 0; @@ -1289,7 +1279,7 @@ pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) funcctx->max_calls = profile->count; /* Make tuple descriptor */ - tupdesc = CreateTemplateTupleDesc(14); + tupdesc = CreateTemplateTupleDesc(15); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", @@ -1304,19 +1294,21 @@ pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) INT8OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 7, "parallel_leader_pid", INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 8, "backend_type", + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "is_regular_backend", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_type", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_state", + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "backend_state", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 10, "proc_start", + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "proc_start", TIMESTAMPTZOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 11, "client_addr", + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_addr", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_hostname", + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "client_hostname", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 13, "appname", + TupleDescInitEntry(tupdesc, (AttrNumber) 14, "appname", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 14, "count", + TupleDescInitEntry(tupdesc, (AttrNumber) 15, "count", INT8OID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); @@ -1331,8 +1323,8 @@ pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) if (funcctx->call_cntr < funcctx->max_calls) { /* for each row */ - Datum values[14]; - bool nulls[14]; + Datum values[15]; + bool nulls[15]; HeapTuple tuple; ProfileItem *item; const char *event_type, @@ -1348,14 +1340,14 @@ pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) MemSet(nulls, 0, sizeof(nulls)); /* Make and return next tuple to caller */ - event_type = pgstat_get_wait_event_type(item->wait_event_info); - event = pgstat_get_wait_event(item->wait_event_info); - backend_type = GetBackendTypeDesc(item->backend_type); - backend_state = GetBackendState(item->backend_state, &is_null_be_state); - proc_start = TimestampTzGetDatum(item->proc_start); - client_addr = get_backend_client_addr(item->client_addr, &is_null_client_addr); - - values[0] = Int32GetDatum(item->pid); + event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); + event = pgstat_get_wait_event(item->dimensions.wait_event_info); + backend_type = GetBackendTypeDesc(item->dimensions.backend_type); + backend_state = GetBackendState(item->dimensions.backend_state, &is_null_be_state); + proc_start = TimestampTzGetDatum(item->dimensions.proc_start); + client_addr = get_backend_client_addr(item->dimensions.client_addr, &is_null_client_addr); + + values[0] = Int32GetDatum(item->dimensions.pid); if (event_type) values[1] = PointerGetDatum(cstring_to_text(event_type)); else @@ -1365,47 +1357,51 @@ pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) else nulls[2] = true; if (pgws_profileQueries) - values[3] = UInt64GetDatum(item->queryId); + values[3] = UInt64GetDatum(item->dimensions.queryId); else values[3] = (Datum) 0; if (pgws_profile_dimensions & PGWS_DIMENSIONS_ROLE_ID) - values[4] = ObjectIdGetDatum(item->role_id); + values[4] = ObjectIdGetDatum(item->dimensions.role_id); else nulls[4] = true; if (pgws_profile_dimensions & PGWS_DIMENSIONS_DB_ID) - values[5] = ObjectIdGetDatum(item->database_id); + values[5] = ObjectIdGetDatum(item->dimensions.database_id); else nulls[5] = true; if (pgws_profile_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) - values[6] = Int32GetDatum(item->parallel_leader_pid); + values[6] = Int32GetDatum(item->dimensions.parallel_leader_pid); else nulls[6] = true; - if (backend_type && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE)) - values[7] = PointerGetDatum(cstring_to_text(backend_type)); + if (pgws_profile_dimensions & PGWS_DIMENSIONS_IS_REGULAR_BE) + values[7] = BoolGetDatum(item->dimensions.is_regular_backend); else nulls[7] = true; - if (!is_null_be_state && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE)) - values[8] = backend_state; + if (backend_type && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE)) + values[8] = PointerGetDatum(cstring_to_text(backend_type)); else nulls[8] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) - values[9] = proc_start; + if (!is_null_be_state && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE)) + values[9] = backend_state; else nulls[9] = true; - if (!is_null_client_addr && pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) - values[10] = client_addr; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) + values[10] = proc_start; else nulls[10] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) - values[11] = PointerGetDatum(cstring_to_text(item->client_hostname)); + if (!is_null_client_addr && pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) + values[11] = client_addr; else nulls[11] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) - values[12] = PointerGetDatum(cstring_to_text(item->appname)); + if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + values[12] = PointerGetDatum(cstring_to_text(item->dimensions.client_hostname)); else nulls[12] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) + values[13] = PointerGetDatum(cstring_to_text(item->dimensions.appname)); + else + nulls[13] = true; - values[13] = UInt64GetDatum(item->count); + values[14] = UInt64GetDatum(item->count); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); @@ -1512,9 +1508,9 @@ pg_wait_sampling_get_history(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - event_type = pgstat_get_wait_event_type(item->wait_event_info); - event = pgstat_get_wait_event(item->wait_event_info); - values[0] = Int32GetDatum(item->pid); + event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); + event = pgstat_get_wait_event(item->dimensions.wait_event_info); + values[0] = Int32GetDatum(item->dimensions.pid); values[1] = TimestampTzGetDatum(item->ts); if (event_type) values[2] = PointerGetDatum(cstring_to_text(event_type)); @@ -1525,7 +1521,7 @@ pg_wait_sampling_get_history(PG_FUNCTION_ARGS) else nulls[3] = true; - values[4] = UInt64GetDatum(item->queryId); + values[4] = UInt64GetDatum(item->dimensions.queryId); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); history->index++; @@ -1566,7 +1562,7 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) funcctx->max_calls = history->count; /* Make tuple descriptor */ - tupdesc = CreateTemplateTupleDesc(14); + tupdesc = CreateTemplateTupleDesc(15); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sample_ts", @@ -1583,17 +1579,19 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) INT8OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 8, "parallel_leader_pid", INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_type", + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "is_regular_backend", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "backend_type", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 10, "backend_state", + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "backend_state", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 11, "proc_start", + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "proc_start", TIMESTAMPTZOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_addr", + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "client_addr", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 13, "client_hostname", + TupleDescInitEntry(tupdesc, (AttrNumber) 14, "client_hostname", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 14, "appname", + TupleDescInitEntry(tupdesc, (AttrNumber) 15, "appname", TEXTOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); @@ -1624,14 +1622,14 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - event_type = pgstat_get_wait_event_type(item->wait_event_info); - event = pgstat_get_wait_event(item->wait_event_info); - backend_type = GetBackendTypeDesc(item->backend_type); - backend_state = GetBackendState(item->backend_state, &is_null_be_state); - proc_start = TimestampTzGetDatum(item->proc_start); - client_addr = get_backend_client_addr(item->client_addr, &is_null_client_addr); + event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); + event = pgstat_get_wait_event(item->dimensions.wait_event_info); + backend_type = GetBackendTypeDesc(item->dimensions.backend_type); + backend_state = GetBackendState(item->dimensions.backend_state, &is_null_be_state); + proc_start = TimestampTzGetDatum(item->dimensions.proc_start); + client_addr = get_backend_client_addr(item->dimensions.client_addr, &is_null_client_addr); - values[0] = Int32GetDatum(item->pid); + values[0] = Int32GetDatum(item->dimensions.pid); values[1] = TimestampTzGetDatum(item->ts); if (event_type) values[2] = PointerGetDatum(cstring_to_text(event_type)); @@ -1641,43 +1639,47 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) values[3] = PointerGetDatum(cstring_to_text(event)); else nulls[3] = true; - values[4] = UInt64GetDatum(item->queryId); + values[4] = UInt64GetDatum(item->dimensions.queryId); if (pgws_profile_dimensions & PGWS_DIMENSIONS_ROLE_ID) - values[5] = ObjectIdGetDatum(item->role_id); + values[5] = ObjectIdGetDatum(item->dimensions.role_id); else nulls[5] = true; if (pgws_profile_dimensions & PGWS_DIMENSIONS_DB_ID) - values[6] = ObjectIdGetDatum(item->database_id); + values[6] = ObjectIdGetDatum(item->dimensions.database_id); else nulls[6] = true; if (pgws_profile_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) - values[7] = Int32GetDatum(item->parallel_leader_pid); + values[7] = Int32GetDatum(item->dimensions.parallel_leader_pid); else nulls[7] = true; - if (backend_type && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE)) - values[8] = PointerGetDatum(cstring_to_text(backend_type)); + if (pgws_profile_dimensions & PGWS_DIMENSIONS_IS_REGULAR_BE) + values[8] = BoolGetDatum(item->dimensions.is_regular_backend); else nulls[8] = true; - if (!is_null_be_state && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE)) - values[9] = backend_state; + if (backend_type && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE)) + values[9] = PointerGetDatum(cstring_to_text(backend_type)); else nulls[9] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) - values[10] = proc_start; + if (!is_null_be_state && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE)) + values[10] = backend_state; else nulls[10] = true; - if (!is_null_client_addr && pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) - values[11] = client_addr; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) + values[11] = proc_start; else nulls[11] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) - values[12] = PointerGetDatum(cstring_to_text(item->client_hostname)); + if (!is_null_client_addr && pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) + values[12] = client_addr; else nulls[12] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) - values[13] = PointerGetDatum(cstring_to_text(item->appname)); + if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + values[13] = PointerGetDatum(cstring_to_text(item->dimensions.client_hostname)); else nulls[13] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) + values[14] = PointerGetDatum(cstring_to_text(item->dimensions.appname)); + else + nulls[14] = true; tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); diff --git a/pg_wait_sampling.h b/pg_wait_sampling.h index 9009fe8..0216f47 100644 --- a/pg_wait_sampling.h +++ b/pg_wait_sampling.h @@ -30,9 +30,10 @@ /* Values for sampling dimensions */ #define PGWS_DIMENSIONS_NONE 0 -#define PGWS_DIMENSIONS_ROLE_ID (1 << 1) -#define PGWS_DIMENSIONS_DB_ID (1 << 2) -#define PGWS_DIMENSIONS_PARALLEL_LEADER_PID (1 << 3) +#define PGWS_DIMENSIONS_ROLE_ID (1 << 0) +#define PGWS_DIMENSIONS_DB_ID (1 << 1) +#define PGWS_DIMENSIONS_PARALLEL_LEADER_PID (1 << 2) +#define PGWS_DIMENSIONS_IS_REGULAR_BE (1 << 3) #define PGWS_DIMENSIONS_BE_TYPE (1 << 4) #define PGWS_DIMENSIONS_BE_STATE (1 << 5) #define PGWS_DIMENSIONS_BE_START_TIME (1 << 6) @@ -44,41 +45,41 @@ /* ^ all 1 in binary */ /* - * Next two structures must match in fields until count/ts so make_profile_hash - * works properly + * Common data (sampling dimenstions) for ProfileItem and HistoryItem */ typedef struct { + /* Fields from PGPROC */ int pid; uint32 wait_event_info; uint64 queryId; Oid role_id; Oid database_id; int parallel_leader_pid; + bool is_regular_backend; + /* Fields from BackendStatus */ BackendType backend_type; BackendState backend_state; TimestampTz proc_start; SockAddr client_addr; char client_hostname[NAMEDATALEN]; char appname[NAMEDATALEN]; - uint64 count; +} SamplingDimensions; + +/* + * Next two structures must match in fields until count/ts so make_profile_hash + * works properly + */ +typedef struct +{ + SamplingDimensions dimensions; + uint64 count; } ProfileItem; typedef struct { - int pid; - uint32 wait_event_info; - uint64 queryId; - Oid role_id; - Oid database_id; - int parallel_leader_pid; - BackendType backend_type; - BackendState backend_state; - TimestampTz proc_start; - SockAddr client_addr; - char client_hostname[NAMEDATALEN]; - char appname[NAMEDATALEN]; - TimestampTz ts; + SamplingDimensions dimensions; + TimestampTz ts; } HistoryItem; typedef struct From 292aaa9d19d12f372db05f2ab64b4dd31439415f Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Tue, 10 Jun 2025 12:59:23 +0700 Subject: [PATCH 04/11] Fixes after review, part 2 --- collector.c | 2 +- pg_wait_sampling.c | 402 ++++++++++++++------------------------------- pg_wait_sampling.h | 3 + 3 files changed, 124 insertions(+), 283 deletions(-) diff --git a/collector.c b/collector.c index 8b1f5a1..f74251c 100644 --- a/collector.c +++ b/collector.c @@ -149,7 +149,7 @@ get_next_observation(History *observations) return result; } -static void +void fill_dimensions(SamplingDimensions *dimensions, PGPROC *proc, int pid, uint32 wait_event_info, uint64 queryId, int dimensions_mask) diff --git a/pg_wait_sampling.c b/pg_wait_sampling.c index d0ebd10..221ec10 100644 --- a/pg_wait_sampling.c +++ b/pg_wait_sampling.c @@ -853,6 +853,114 @@ get_beentry_by_procpid(int pid) return NULL; } +/* + * Common routine to fill "dimensions" part of tupdesc + */ +static void +fill_tuple_desc (TupleDesc tupdesc) +{ + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "role_id", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "database_id", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "parallel_leader_pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "is_regular_backend", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "backend_state", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "proc_start", + TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_addr", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "client_hostname", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 14, "appname", + TEXTOID, -1, 0); +} + +static void +fill_values_and_nulls(Datum *values, bool *nulls, SamplingDimensions dimensions, bool skip_mask) +{ + const char *event_type, + *event, + *backend_type; + Datum backend_state, proc_start, client_addr; + bool is_null_be_state = false, + is_null_client_addr = false; + + event_type = pgstat_get_wait_event_type(dimensions.wait_event_info); + event = pgstat_get_wait_event(dimensions.wait_event_info); + backend_type = GetBackendTypeDesc(dimensions.backend_type); + backend_state = GetBackendState(dimensions.backend_state, &is_null_be_state); + proc_start = TimestampTzGetDatum(dimensions.proc_start); + client_addr = get_backend_client_addr(dimensions.client_addr, &is_null_client_addr); + + values[0] = Int32GetDatum(dimensions.pid); + if (event_type) + values[1] = PointerGetDatum(cstring_to_text(event_type)); + else + nulls[1] = true; + if (event) + values[2] = PointerGetDatum(cstring_to_text(event)); + else + nulls[2] = true; + if (pgws_profileQueries || skip_mask) + values[3] = UInt64GetDatum(dimensions.queryId); + else + values[3] = (Datum) 0; + if ((pgws_profile_dimensions & PGWS_DIMENSIONS_ROLE_ID) || skip_mask) + values[4] = ObjectIdGetDatum(dimensions.role_id); + else + nulls[4] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_DB_ID || skip_mask) + values[5] = ObjectIdGetDatum(dimensions.database_id); + else + nulls[5] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID || skip_mask) + values[6] = Int32GetDatum(dimensions.parallel_leader_pid); + else + nulls[6] = true; + if (pgws_profile_dimensions & PGWS_DIMENSIONS_IS_REGULAR_BE || skip_mask) + values[7] = BoolGetDatum(dimensions.is_regular_backend); + else + nulls[7] = true; + if (backend_type && ((pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE) || skip_mask)) + values[8] = PointerGetDatum(cstring_to_text(backend_type)); + else + nulls[8] = true; + if (!is_null_be_state && ((pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE) || skip_mask)) + values[9] = backend_state; + else + nulls[9] = true; + if ((pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) || skip_mask) + values[10] = proc_start; + else + nulls[10] = true; + if (!is_null_client_addr && ((pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) || skip_mask)) + values[11] = client_addr; + else + nulls[11] = true; + if ((pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) || skip_mask) + values[12] = PointerGetDatum(cstring_to_text(dimensions.client_hostname)); + else + nulls[12] = true; + if ((pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) || skip_mask) + values[13] = PointerGetDatum(cstring_to_text(dimensions.appname)); + else + nulls[13] = true; +} + PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current_extended); Datum pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) @@ -877,35 +985,7 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) funcctx->user_fctx = params; /* Setup tuple desc */ tupdesc = CreateTemplateTupleDesc(14); - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 5, "role_id", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 6, "database_id", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 7, "parallel_leader_pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 8, "is_regular_backend", - BOOLOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 10, "backend_state", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 11, "proc_start", - TIMESTAMPTZOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_addr", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 13, "client_hostname", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 14, "appname", - TEXTOID, -1, 0); - + fill_tuple_desc (tupdesc); funcctx->tuple_desc = BlessTupleDesc(tupdesc); LWLockAcquire(ProcArrayLock, LW_SHARED); @@ -951,25 +1031,6 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) pgws_proc_queryids[proc - ProcGlobal->allProcs], PGWS_DIMENSIONS_ALL); -// /* Show all fields without looking at GUC variables */ -// params->items[j].dimensions.pid = proc.pid; -// params->items[j].dimensions.wait_event_info = proc.wait_event_info; -// params->items[j].dimensions.queryId = pgws_proc_queryids[i]; -// params->items[j].dimensions.role_id = proc.roleId; -// params->items[j].dimensions.database_id = proc.databaseId; -// params->items[j].dimensions.parallel_leader_pid = (proc.lockGroupLeader ? -// proc.lockGroupLeader->pid : -// 0); -// params->items[j].dimensions.is_regular_backend = proc.isRegularBackend; -// if (bestatus) -// { -// params->items[j].dimensions.backend_type = bestatus->st_backendType; -// params->items[j].dimensions.backend_state = bestatus->st_state; -// params->items[j].dimensions.proc_start = bestatus->st_proc_start_timestamp; -// params->items[j].dimensions.client_addr = bestatus->st_clientaddr; -// strcpy(params->items[j].dimensions.client_hostname, bestatus->st_clienthostname); -// strcpy(params->items[j].dimensions.appname, bestatus->st_appname); -// } j++; } funcctx->max_calls = j; @@ -994,12 +1055,6 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) HeapTuple tuple; Datum values[14]; bool nulls[14]; - const char *event_type, - *event, - *backend_type; - Datum backend_state, proc_start, client_addr; - bool is_null_be_state = false, - is_null_client_addr = false; HistoryItem *item; item = ¶ms->items[funcctx->call_cntr]; @@ -1008,42 +1063,7 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); - event = pgstat_get_wait_event(item->dimensions.wait_event_info); - backend_type = GetBackendTypeDesc(item->dimensions.backend_type); - backend_state = GetBackendState(item->dimensions.backend_state, &is_null_be_state); - proc_start = TimestampTzGetDatum(item->dimensions.proc_start); - client_addr = get_backend_client_addr(item->dimensions.client_addr, &is_null_client_addr); - - values[0] = Int32GetDatum(item->dimensions.pid); - if (event_type) - values[1] = PointerGetDatum(cstring_to_text(event_type)); - else - nulls[1] = true; - if (event) - values[2] = PointerGetDatum(cstring_to_text(event)); - else - nulls[2] = true; - values[3] = UInt64GetDatum(item->dimensions.queryId); - values[4] = ObjectIdGetDatum(item->dimensions.role_id); - values[5] = ObjectIdGetDatum(item->dimensions.database_id); - values[6] = Int32GetDatum(item->dimensions.parallel_leader_pid); - values[7] = BoolGetDatum(item->dimensions.is_regular_backend); - if (backend_type) - values[7] = PointerGetDatum(cstring_to_text(backend_type)); - else - nulls[7] = true; - if (!is_null_be_state) - values[8] = backend_state; - else - nulls[8] = true; - values[9] = proc_start; - if (!is_null_client_addr) - values[10] = client_addr; - else - nulls[10] = true; - values[11] = PointerGetDatum(cstring_to_text(item->dimensions.client_hostname)); - values[12] = PointerGetDatum(cstring_to_text(item->dimensions.appname)); + fill_values_and_nulls(values, nulls, item->dimensions, true); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); @@ -1280,34 +1300,7 @@ pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) /* Make tuple descriptor */ tupdesc = CreateTemplateTupleDesc(15); - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 5, "role_id", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 6, "database_id", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 7, "parallel_leader_pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 8, "is_regular_backend", - BOOLOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 10, "backend_state", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 11, "proc_start", - TIMESTAMPTZOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_addr", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 13, "client_hostname", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 14, "appname", - TEXTOID, -1, 0); + fill_tuple_desc (tupdesc); TupleDescInitEntry(tupdesc, (AttrNumber) 15, "count", INT8OID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); @@ -1327,82 +1320,16 @@ pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) bool nulls[15]; HeapTuple tuple; ProfileItem *item; - const char *event_type, - *event, - *backend_type; - Datum backend_state, proc_start, client_addr; - bool is_null_be_state = false, - is_null_client_addr = false; item = &profile->items[funcctx->call_cntr]; + /* Make and return next tuple to caller */ MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - /* Make and return next tuple to caller */ - event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); - event = pgstat_get_wait_event(item->dimensions.wait_event_info); - backend_type = GetBackendTypeDesc(item->dimensions.backend_type); - backend_state = GetBackendState(item->dimensions.backend_state, &is_null_be_state); - proc_start = TimestampTzGetDatum(item->dimensions.proc_start); - client_addr = get_backend_client_addr(item->dimensions.client_addr, &is_null_client_addr); - - values[0] = Int32GetDatum(item->dimensions.pid); - if (event_type) - values[1] = PointerGetDatum(cstring_to_text(event_type)); - else - nulls[1] = true; - if (event) - values[2] = PointerGetDatum(cstring_to_text(event)); - else - nulls[2] = true; - if (pgws_profileQueries) - values[3] = UInt64GetDatum(item->dimensions.queryId); - else - values[3] = (Datum) 0; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_ROLE_ID) - values[4] = ObjectIdGetDatum(item->dimensions.role_id); - else - nulls[4] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_DB_ID) - values[5] = ObjectIdGetDatum(item->dimensions.database_id); - else - nulls[5] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) - values[6] = Int32GetDatum(item->dimensions.parallel_leader_pid); - else - nulls[6] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_IS_REGULAR_BE) - values[7] = BoolGetDatum(item->dimensions.is_regular_backend); - else - nulls[7] = true; - if (backend_type && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE)) - values[8] = PointerGetDatum(cstring_to_text(backend_type)); - else - nulls[8] = true; - if (!is_null_be_state && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE)) - values[9] = backend_state; - else - nulls[9] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) - values[10] = proc_start; - else - nulls[10] = true; - if (!is_null_client_addr && pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) - values[11] = client_addr; - else - nulls[11] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) - values[12] = PointerGetDatum(cstring_to_text(item->dimensions.client_hostname)); - else - nulls[12] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) - values[13] = PointerGetDatum(cstring_to_text(item->dimensions.appname)); - else - nulls[13] = true; - + fill_values_and_nulls(values, nulls, item->dimensions, false); values[14] = UInt64GetDatum(item->count); - + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); @@ -1563,36 +1490,9 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) /* Make tuple descriptor */ tupdesc = CreateTemplateTupleDesc(15); - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sample_ts", - TIMESTAMPTZOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "event", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 5, "queryid", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 6, "role_id", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 7, "database_id", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 8, "parallel_leader_pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 9, "is_regular_backend", - BOOLOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 10, "backend_type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 11, "backend_state", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 12, "proc_start", + fill_tuple_desc (tupdesc); + TupleDescInitEntry(tupdesc, (AttrNumber) 15, "sample_ts", //TODO we have moved this to the end to have it more in line with current and profile; debatable; maybe move it to first place? TIMESTAMPTZOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 13, "client_addr", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 14, "client_hostname", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 15, "appname", - TEXTOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); MemoryContextSwitchTo(oldcontext); @@ -1607,14 +1507,8 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) { HeapTuple tuple; HistoryItem *item; - Datum values[14]; - bool nulls[14]; - const char *event_type, - *event, - *backend_type; - Datum backend_state, proc_start, client_addr; - bool is_null_be_state = false, - is_null_client_addr = false; + Datum values[15]; + bool nulls[15]; item = &history->items[history->index]; @@ -1622,64 +1516,8 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); - event = pgstat_get_wait_event(item->dimensions.wait_event_info); - backend_type = GetBackendTypeDesc(item->dimensions.backend_type); - backend_state = GetBackendState(item->dimensions.backend_state, &is_null_be_state); - proc_start = TimestampTzGetDatum(item->dimensions.proc_start); - client_addr = get_backend_client_addr(item->dimensions.client_addr, &is_null_client_addr); - - values[0] = Int32GetDatum(item->dimensions.pid); - values[1] = TimestampTzGetDatum(item->ts); - if (event_type) - values[2] = PointerGetDatum(cstring_to_text(event_type)); - else - nulls[2] = true; - if (event) - values[3] = PointerGetDatum(cstring_to_text(event)); - else - nulls[3] = true; - values[4] = UInt64GetDatum(item->dimensions.queryId); - if (pgws_profile_dimensions & PGWS_DIMENSIONS_ROLE_ID) - values[5] = ObjectIdGetDatum(item->dimensions.role_id); - else - nulls[5] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_DB_ID) - values[6] = ObjectIdGetDatum(item->dimensions.database_id); - else - nulls[6] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) - values[7] = Int32GetDatum(item->dimensions.parallel_leader_pid); - else - nulls[7] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_IS_REGULAR_BE) - values[8] = BoolGetDatum(item->dimensions.is_regular_backend); - else - nulls[8] = true; - if (backend_type && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE)) - values[9] = PointerGetDatum(cstring_to_text(backend_type)); - else - nulls[9] = true; - if (!is_null_be_state && (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE)) - values[10] = backend_state; - else - nulls[10] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) - values[11] = proc_start; - else - nulls[11] = true; - if (!is_null_client_addr && pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) - values[12] = client_addr; - else - nulls[12] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) - values[13] = PointerGetDatum(cstring_to_text(item->dimensions.client_hostname)); - else - nulls[13] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) - values[14] = PointerGetDatum(cstring_to_text(item->dimensions.appname)); - else - nulls[14] = true; + fill_values_and_nulls(values, nulls, item->dimensions, false); + values[14] = TimestampTzGetDatum(item->ts); //TODO!!!!!!!!!!! tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); diff --git a/pg_wait_sampling.h b/pg_wait_sampling.h index 0216f47..ce7a721 100644 --- a/pg_wait_sampling.h +++ b/pg_wait_sampling.h @@ -123,6 +123,9 @@ extern int pgws_profile_dimensions; /* bit mask that is derived from GUC */ extern PgBackendStatus* get_beentry_by_procpid(int pid); /* collector.c */ +extern void fill_dimensions(SamplingDimensions *dimensions, PGPROC *proc, + int pid, uint32 wait_event_info, uint64 queryId, + int dimensions_mask) extern void pgws_register_wait_collector(void); extern PGDLLEXPORT void pgws_collector_main(Datum main_arg); From da75db063b1aa83cc28366914619fecbb697c2b6 Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Wed, 11 Jun 2025 17:16:40 +0700 Subject: [PATCH 05/11] Add serialization --- collector.c | 361 +++++++++++++++-- pg_wait_sampling--1.1--1.2.sql | 9 +- pg_wait_sampling.c | 700 ++++++++++++++++++--------------- pg_wait_sampling.h | 35 +- 4 files changed, 739 insertions(+), 366 deletions(-) diff --git a/collector.c b/collector.c index f74251c..180fcce 100644 --- a/collector.c +++ b/collector.c @@ -40,6 +40,9 @@ PGWS_DIMENSIONS_APPNAME)) static volatile sig_atomic_t shutdown_requested = false; +int saved_profile_dimensions; //TODO should be initialized with the same value as GUC? +int saved_history_dimensions; + static void handle_sigterm(SIGNAL_ARGS); /* @@ -73,6 +76,8 @@ alloc_history(History *observations, int count) observations->index = 0; observations->count = count; observations->wraparound = false; + + saved_history_dimensions = pgws_history_dimensions; } /* @@ -117,6 +122,8 @@ realloc_history(History *observations, int count) observations->index = copyCount; observations->count = count; observations->wraparound = false; + + saved_history_dimensions = pgws_history_dimensions; } static void @@ -157,13 +164,20 @@ fill_dimensions(SamplingDimensions *dimensions, PGPROC *proc, Oid role_id = proc->roleId; Oid database_id = proc->databaseId; PGPROC *lockGroupLeader = proc->lockGroupLeader; +#if PG_VERSION_NUM >= 180000 bool is_regular_backend = proc->isRegularBackend; +#else + bool is_regular_backend = !proc->isBackgroundWorker; +#endif - dimensions->pid = pid; + if (dimensions_mask & PGWS_DIMENSIONS_PID) + dimensions->pid = pid; - dimensions->wait_event_info = wait_event_info; + if (dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT_TYPE || + dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT) + dimensions->wait_event_info = wait_event_info; - if (pgws_profileQueries) + if (pgws_profileQueries || (dimensions_mask & PGWD_DIMENSIONS_QUERY_ID)) dimensions->queryId = queryId; /* Copy everything we need from PGPROC */ @@ -217,13 +231,17 @@ static void copy_dimensions (SamplingDimensions *dst, SamplingDimensions *src, int dst_dimensions_mask) { - dst->pid = src->pid; + if (dst_dimensions_mask & PGWS_DIMENSIONS_PID) + dst->pid = src->pid; - dst->wait_event_info = src->wait_event_info; + if (dst_dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT_TYPE || + dst_dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT) + dst->wait_event_info = src->wait_event_info; - dst->queryId = src->queryId; + if (dst_dimensions_mask & PGWD_DIMENSIONS_QUERY_ID) + dst->queryId = src->queryId; - if (dst_dimensions_mask & PGWS_DIMENSIONS_ROLE_ID) + if (dst_dimensions_mask & PGWD_DIMENSIONS_QUERY_ID) dst->role_id = src->role_id; if (dst_dimensions_mask & PGWS_DIMENSIONS_DB_ID) @@ -254,6 +272,283 @@ copy_dimensions (SamplingDimensions *dst, SamplingDimensions *src, strcpy(dst->appname, src->appname); } +int +get_serialized_size(int dimensions_mask, bool need_last_field) +{ + int serialized_size = 0; + SamplingDimensions dimensions = {0}; /* Used only for sizeof */ + + if (dimensions_mask & PGWS_DIMENSIONS_PID) + serialized_size += sizeof(dimensions.pid); + if (dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT_TYPE || + dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT) + serialized_size += sizeof(dimensions.wait_event_info); + if (dimensions_mask & PGWD_DIMENSIONS_QUERY_ID) + serialized_size += sizeof(dimensions.queryId); + if (dimensions_mask & PGWS_DIMENSIONS_ROLE_ID) + serialized_size += sizeof(dimensions.role_id); + if (dimensions_mask & PGWS_DIMENSIONS_DB_ID) + serialized_size += sizeof(dimensions.database_id); + if (dimensions_mask & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + serialized_size += sizeof(dimensions.parallel_leader_pid); + if (dimensions_mask & PGWS_DIMENSIONS_IS_REGULAR_BE) + serialized_size += sizeof(dimensions.is_regular_backend); + if (dimensions_mask & PGWS_DIMENSIONS_BE_TYPE) + serialized_size += sizeof(dimensions.backend_type); + if (dimensions_mask & PGWS_DIMENSIONS_BE_STATE) + serialized_size += sizeof(dimensions.backend_state); + if (dimensions_mask & PGWS_DIMENSIONS_BE_START_TIME) + serialized_size += sizeof(dimensions.proc_start); + if (dimensions_mask & PGWS_DIMENSIONS_CLIENT_ADDR) + serialized_size += sizeof(dimensions.client_addr); + if (dimensions_mask & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + serialized_size += sizeof(dimensions.client_hostname); + if (dimensions_mask & PGWS_DIMENSIONS_APPNAME) + serialized_size += sizeof(dimensions.appname); + /* timestamp of history and count of profile are both 8 bytes */ + if (need_last_field) + serialized_size += sizeof(uint64); + return serialized_size; +} + +static void +serialize_item(SamplingDimensions dimensions, int dimensions_mask, + char **serialized_item, char **serialized_key, int *serialized_size, + TimestampTz ts, uint64 count, bool is_history) +{ + char dummy_array[sizeof(SamplingDimensions) + sizeof(uint64) + 1]; + + memset(dummy_array, 0, sizeof(dummy_array)); + + if (dimensions_mask & PGWS_DIMENSIONS_PID) + { + memcpy(dummy_array + *serialized_size, &dimensions.pid, + sizeof(dimensions.pid)); + *serialized_size += sizeof(dimensions.pid); + } + + if (dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT_TYPE || + dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT) + { + memcpy(dummy_array + *serialized_size, &dimensions.wait_event_info, + sizeof(dimensions.wait_event_info)); + *serialized_size += sizeof(dimensions.wait_event_info); + } + + if (dimensions_mask & PGWD_DIMENSIONS_QUERY_ID) + { + memcpy(dummy_array + *serialized_size, &dimensions.queryId, + sizeof(dimensions.queryId)); + *serialized_size += sizeof(dimensions.queryId); + } + + if (dimensions_mask & PGWS_DIMENSIONS_ROLE_ID) + { + memcpy(dummy_array + *serialized_size, &dimensions.role_id, + sizeof(dimensions.role_id)); + *serialized_size += sizeof(dimensions.role_id); + } + + if (dimensions_mask & PGWS_DIMENSIONS_DB_ID) + { + memcpy(dummy_array + *serialized_size, &dimensions.database_id, + sizeof(dimensions.database_id)); + *serialized_size += sizeof(dimensions.database_id); + } + + if (dimensions_mask & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + { + memcpy(dummy_array + *serialized_size, &dimensions.parallel_leader_pid, + sizeof(dimensions.parallel_leader_pid)); + *serialized_size += sizeof(dimensions.parallel_leader_pid); + } + + if (dimensions_mask & PGWS_DIMENSIONS_IS_REGULAR_BE) + { + memcpy(dummy_array + *serialized_size, &dimensions.is_regular_backend, + sizeof(dimensions.is_regular_backend)); + *serialized_size += sizeof(dimensions.is_regular_backend); + } + + if (dimensions_mask & PGWS_DIMENSIONS_BE_TYPE) + { + memcpy(dummy_array + *serialized_size, &dimensions.backend_type, + sizeof(dimensions.backend_type)); + *serialized_size += sizeof(dimensions.backend_type); + } + + if (dimensions_mask & PGWS_DIMENSIONS_BE_STATE) + { + memcpy(dummy_array + *serialized_size, &dimensions.backend_state, + sizeof(dimensions.backend_state)); + *serialized_size += sizeof(dimensions.backend_state); + } + + if (dimensions_mask & PGWS_DIMENSIONS_BE_START_TIME) + { + memcpy(dummy_array + *serialized_size, &dimensions.proc_start, + sizeof(dimensions.proc_start)); + *serialized_size += sizeof(dimensions.proc_start); + } + + if (dimensions_mask & PGWS_DIMENSIONS_CLIENT_ADDR) + { + memcpy(dummy_array + *serialized_size, &dimensions.client_addr, + sizeof(dimensions.client_addr)); + *serialized_size += sizeof(dimensions.client_addr); + } + + if (dimensions_mask & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + { + memcpy(dummy_array + *serialized_size, &dimensions.client_hostname, + sizeof(dimensions.client_hostname)); + *serialized_size += sizeof(dimensions.client_hostname); + } + + if (dimensions_mask & PGWS_DIMENSIONS_APPNAME) + { + memcpy(dummy_array + *serialized_size, &dimensions.appname, + sizeof(dimensions.appname)); + *serialized_size += sizeof(dimensions.appname); + } + + /* copy all the fields without ts/count */ + *serialized_key = palloc0(*serialized_size + 1); + strcpy(*serialized_key, dummy_array); + + if (is_history) + { + memcpy(dummy_array + *serialized_size, &ts, + sizeof(TimestampTz)); + *serialized_size += sizeof(TimestampTz); + } + else + { + memcpy(dummy_array + *serialized_size, &count, + sizeof(uint64)); + *serialized_size += sizeof(uint64); + } + + /* copy everything */ + *serialized_item = palloc0(*serialized_size + 1); + strcpy(*serialized_item, dummy_array); +} + +void +deserialize_item(SamplingDimensions *dimensions, char *serialized_item, + int dimensions_mask, TimestampTz *ts, uint64 *count) +{ + int idx = 0; + + memset(dimensions, 0, sizeof(SamplingDimensions)); + + if (dimensions_mask & PGWS_DIMENSIONS_PID) + { + memcpy(&dimensions->pid, serialized_item + idx, + sizeof(dimensions->pid)); + idx += sizeof(dimensions->pid); + } + + if (dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT_TYPE || + dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT) + { + memcpy(&dimensions->wait_event_info, serialized_item + idx, + sizeof(dimensions->wait_event_info)); + idx += sizeof(dimensions->wait_event_info); + } + + if (dimensions_mask & PGWD_DIMENSIONS_QUERY_ID) + { + memcpy(&dimensions->queryId, serialized_item + idx, + sizeof(dimensions->queryId)); + idx += sizeof(dimensions->queryId); + } + + if (dimensions_mask & PGWS_DIMENSIONS_ROLE_ID) + { + memcpy(&dimensions->role_id, serialized_item + idx, + sizeof(dimensions->role_id)); + idx += sizeof(dimensions->role_id); + } + + if (dimensions_mask & PGWS_DIMENSIONS_DB_ID) + { + memcpy(&dimensions->database_id, serialized_item + idx, + sizeof(dimensions->database_id)); + idx += sizeof(dimensions->database_id); + } + + if (dimensions_mask & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + { + memcpy(&dimensions->parallel_leader_pid, serialized_item + idx, + sizeof(dimensions->parallel_leader_pid)); + idx += sizeof(dimensions->parallel_leader_pid); + } + + if (dimensions_mask & PGWS_DIMENSIONS_IS_REGULAR_BE) + { + memcpy(&dimensions->is_regular_backend, serialized_item + idx, + sizeof(dimensions->is_regular_backend)); + idx += sizeof(dimensions->is_regular_backend); + } + + if (dimensions_mask & PGWS_DIMENSIONS_BE_TYPE) + { + memcpy(&dimensions->backend_type, serialized_item + idx, + sizeof(dimensions->backend_type)); + idx += sizeof(dimensions->backend_type); + } + + if (dimensions_mask & PGWS_DIMENSIONS_BE_STATE) + { + memcpy(&dimensions->backend_state, serialized_item + idx, + sizeof(dimensions->backend_state)); + idx += sizeof(dimensions->backend_state); + } + + if (dimensions_mask & PGWS_DIMENSIONS_BE_START_TIME) + { + memcpy(&dimensions->proc_start, serialized_item + idx, + sizeof(dimensions->proc_start)); + idx += sizeof(dimensions->proc_start); + } + + if (dimensions_mask & PGWS_DIMENSIONS_CLIENT_ADDR) + { + memcpy(&dimensions->client_addr, serialized_item + idx, + sizeof(dimensions->client_addr)); + idx += sizeof(dimensions->client_addr); + } + + if (dimensions_mask & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + { + memcpy(&dimensions->client_hostname, serialized_item + idx, + sizeof(dimensions->client_hostname)); + idx += sizeof(dimensions->client_hostname); + } + + if (dimensions_mask & PGWS_DIMENSIONS_APPNAME) + { + memcpy(&dimensions->appname, serialized_item + idx, + sizeof(dimensions->appname)); + idx += sizeof(dimensions->appname); + } + + if (ts) + { + memcpy(ts, serialized_item + idx, + sizeof(TimestampTz)); + idx += sizeof(TimestampTz); + } + + if (count) + { + memcpy(count, serialized_item + idx, + sizeof(uint64)); + idx += sizeof(uint64); + } +} + /* * Read current waits from backends and write them to history array * and/or profile hash. @@ -281,7 +576,9 @@ probe_waits(History *observations, HTAB *profile_hash, PGPROC *proc = &ProcGlobal->allProcs[i]; int pid; uint32 wait_event_info; - SamplingDimensions common_dimensions; + SamplingDimensions common_dimensions, + history_dimensions, + profile_dimensions; int dimensions_mask_common = pgws_history_dimensions | pgws_profile_dimensions; @@ -290,28 +587,30 @@ probe_waits(History *observations, HTAB *profile_hash, continue; /* - * We zero items and dimensions with memset - * to avoid doing it field-by-field + * We zero dimensions with memset to avoid doing it field-by-field */ - memset(&item_history, 0, sizeof(HistoryItem)); - memset(&item_profile, 0, sizeof(ProfileItem)); + memset(&history_dimensions, 0, sizeof(SamplingDimensions)); + memset(&profile_dimensions, 0, sizeof(SamplingDimensions)); memset(&common_dimensions, 0, sizeof(SamplingDimensions)); fill_dimensions(&common_dimensions, proc, pid, wait_event_info, pgws_proc_queryids[i], dimensions_mask_common); - copy_dimensions(&item_history.dimensions, + copy_dimensions(&history_dimensions, &common_dimensions, pgws_history_dimensions); - copy_dimensions(&item_history.dimensions, + copy_dimensions(&profile_dimensions, &common_dimensions, pgws_profile_dimensions); item_history.ts = ts; + item_history.dimensions = history_dimensions; /* Write to the history if needed */ if (write_history) { + //TODO вот тут что-то сделать нужно??? потому что мы не запаковываем + //историю observation = get_next_observation(observations); *observation = item_history; } @@ -319,18 +618,33 @@ probe_waits(History *observations, HTAB *profile_hash, /* Write to the profile if needed */ if (write_profile) { - ProfileItem *profileItem; - bool found; + bool found; + int serialized_size = 0; + uint64 count = 1; + char *serialized_key, + *serialized_item, + *stored_item; if (!profile_pid) item_profile.dimensions.pid = 0; - profileItem = (ProfileItem *) hash_search(profile_hash, &item_profile, - HASH_ENTER, &found); + serialize_item(item_profile.dimensions, saved_profile_dimensions, + &serialized_item, &serialized_key, &serialized_size, + (TimestampTz) 0, count, false); + + stored_item = (char *) hash_search(profile_hash, serialized_key, + HASH_ENTER, &found); + if (found) - profileItem->count++; + { + memcpy(&count, (stored_item + serialized_size - sizeof(uint64)), + sizeof(uint64)); + count++; + memcpy((stored_item + serialized_size - sizeof(uint64)), &count, + sizeof(uint64)); + } else - profileItem->count = 1; + memcpy(stored_item, serialized_item, serialized_size); } } LWLockRelease(ProcArrayLock); @@ -426,14 +740,17 @@ make_profile_hash() { HASHCTL hash_ctl; + saved_profile_dimensions = pgws_profile_dimensions; + /* * Since adding additional dimensions we use SamplingDimensions as * hashtable key. This is fine for cases when some fields are 0 since * it doesn't impede our ability to search the hash table for entries */ - hash_ctl.keysize = sizeof(SamplingDimensions); + hash_ctl.keysize = get_serialized_size(saved_profile_dimensions, false); + /* entry includes SamplingDimensions and ts/count */ + hash_ctl.entrysize = get_serialized_size(saved_profile_dimensions, true); - hash_ctl.entrysize = sizeof(ProfileItem); return hash_create("Waits profile hash", 1024, &hash_ctl, HASH_ELEM | HASH_BLOBS); } diff --git a/pg_wait_sampling--1.1--1.2.sql b/pg_wait_sampling--1.1--1.2.sql index 29def16..e774f0f 100644 --- a/pg_wait_sampling--1.1--1.2.sql +++ b/pg_wait_sampling--1.1--1.2.sql @@ -3,12 +3,9 @@ -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pg_wait_sampling UPDATE TO 1.2" to load this file. \quit -DROP FUNCTION pg_wait_sampling_get_profile ( - OUT pid int4, - OUT event_type text, - OUT event text, - OUT count bigint -) CASCADE; +DROP FUNCTION pg_wait_sampling_get_current CASCADE; +DROP FUNCTION pg_wait_sampling_get_profile CASCADE; +DROP FUNCTION pg_wait_sampling_get_history CASCADE; CREATE FUNCTION pg_wait_sampling_get_current_extended ( pid int4, diff --git a/pg_wait_sampling.c b/pg_wait_sampling.c index 221ec10..29d96ab 100644 --- a/pg_wait_sampling.c +++ b/pg_wait_sampling.c @@ -346,7 +346,15 @@ pgws_general_dimensions_check_hook (char **newvalue, void **extra, GucSource sou char *tok = (char *) lfirst(l); /* Process all allowed values */ - if (pg_strcasecmp(tok, "role_id") == 0) + if (pg_strcasecmp(tok, "pid") == 0) + extrachecks |= PGWS_DIMENSIONS_PID; + else if (pg_strcasecmp(tok, "wait_event_type") == 0) + extrachecks |= PGWS_DIMENSIONS_WAIT_EVENT_TYPE; + else if (pg_strcasecmp(tok, "wait_event") == 0) + extrachecks |= PGWS_DIMENSIONS_WAIT_EVENT; + else if (pg_strcasecmp(tok, "query_id") == 0) + extrachecks |= PGWD_DIMENSIONS_QUERY_ID; + else if (pg_strcasecmp(tok, "role_id") == 0) extrachecks |= PGWS_DIMENSIONS_ROLE_ID; else if (pg_strcasecmp(tok, "database_id") == 0) extrachecks |= PGWS_DIMENSIONS_DB_ID; @@ -536,7 +544,7 @@ _PG_init(void) "Sets sampling dimensions for history", NULL, &pgws_history_dimensions_string, - "none", + "pid, wait_event_type, wait_event, query_id", PGC_SIGHUP, GUC_LIST_INPUT, pgws_general_dimensions_check_hook, @@ -547,7 +555,7 @@ _PG_init(void) "Sets sampling dimensions for profile", NULL, &pgws_profile_dimensions_string, - "none", + "pid, wait_event_type, wait_event, query_id", PGC_SIGHUP, GUC_LIST_INPUT, pgws_general_dimensions_check_hook, @@ -620,127 +628,127 @@ typedef struct TimestampTz ts; } WaitCurrentContext; -PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current); -Datum -pg_wait_sampling_get_current(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - WaitCurrentContext *params; - - check_shmem(); - - if (SRF_IS_FIRSTCALL()) - { - MemoryContext oldcontext; - TupleDesc tupdesc; - - funcctx = SRF_FIRSTCALL_INIT(); - - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - params = (WaitCurrentContext *) palloc0(sizeof(WaitCurrentContext)); - params->ts = GetCurrentTimestamp(); - - funcctx->user_fctx = params; - tupdesc = CreateTemplateTupleDesc(4); - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", - INT8OID, -1, 0); - - funcctx->tuple_desc = BlessTupleDesc(tupdesc); - - LWLockAcquire(ProcArrayLock, LW_SHARED); - - if (!PG_ARGISNULL(0)) - { - /* pg_wait_sampling_get_current(pid int4) function */ - HistoryItem *item; - PGPROC *proc; - - proc = search_proc(PG_GETARG_UINT32(0)); - params->items = (HistoryItem *) palloc0(sizeof(HistoryItem)); - item = ¶ms->items[0]; - item->dimensions.pid = proc->pid; - item->dimensions.wait_event_info = proc->wait_event_info; - item->dimensions.queryId = pgws_proc_queryids[proc - ProcGlobal->allProcs]; - funcctx->max_calls = 1; - } - else - { - /* pg_wait_sampling_current view */ - int procCount = ProcGlobal->allProcCount, - i, - j = 0; - - params->items = (HistoryItem *) palloc0(sizeof(HistoryItem) * procCount); - for (i = 0; i < procCount; i++) - { - PGPROC *proc = &ProcGlobal->allProcs[i]; - - if (!pgws_should_sample_proc(proc, - ¶ms->items[j].dimensions.pid, - ¶ms->items[j].dimensions.wait_event_info)) - continue; - - params->items[j].dimensions.pid = proc->pid; - params->items[j].dimensions.wait_event_info = proc->wait_event_info; - params->items[j].dimensions.queryId = pgws_proc_queryids[i]; - j++; - } - funcctx->max_calls = j; - } - - LWLockRelease(ProcArrayLock); - - MemoryContextSwitchTo(oldcontext); - } - - /* stuff done on every call of the function */ - funcctx = SRF_PERCALL_SETUP(); - params = (WaitCurrentContext *) funcctx->user_fctx; - - if (funcctx->call_cntr < funcctx->max_calls) - { - HeapTuple tuple; - Datum values[4]; - bool nulls[4]; - const char *event_type, - *event; - HistoryItem *item; - - item = ¶ms->items[funcctx->call_cntr]; - - /* Make and return next tuple to caller */ - MemSet(values, 0, sizeof(values)); - MemSet(nulls, 0, sizeof(nulls)); - - event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); - event = pgstat_get_wait_event(item->dimensions.wait_event_info); - values[0] = Int32GetDatum(item->dimensions.pid); - if (event_type) - values[1] = PointerGetDatum(cstring_to_text(event_type)); - else - nulls[1] = true; - if (event) - values[2] = PointerGetDatum(cstring_to_text(event)); - else - nulls[2] = true; - - values[3] = UInt64GetDatum(item->dimensions.queryId); - tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); - - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); - } - else - { - /* nothing left */ - SRF_RETURN_DONE(funcctx); - } -} +//PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current); +//Datum +//pg_wait_sampling_get_current(PG_FUNCTION_ARGS) +//{ +// FuncCallContext *funcctx; +// WaitCurrentContext *params; +// +// check_shmem(); +// +// if (SRF_IS_FIRSTCALL()) +// { +// MemoryContext oldcontext; +// TupleDesc tupdesc; +// +// funcctx = SRF_FIRSTCALL_INIT(); +// +// oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); +// params = (WaitCurrentContext *) palloc0(sizeof(WaitCurrentContext)); +// params->ts = GetCurrentTimestamp(); +// +// funcctx->user_fctx = params; +// tupdesc = CreateTemplateTupleDesc(4); +// TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", +// INT4OID, -1, 0); +// TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", +// TEXTOID, -1, 0); +// TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", +// TEXTOID, -1, 0); +// TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", +// INT8OID, -1, 0); +// +// funcctx->tuple_desc = BlessTupleDesc(tupdesc); +// +// LWLockAcquire(ProcArrayLock, LW_SHARED); +// +// if (!PG_ARGISNULL(0)) +// { +// /* pg_wait_sampling_get_current(pid int4) function */ +// HistoryItem *item; +// PGPROC *proc; +// +// proc = search_proc(PG_GETARG_UINT32(0)); +// params->items = (HistoryItem *) palloc0(sizeof(HistoryItem)); +// item = ¶ms->items[0]; +// item->dimensions.pid = proc->pid; +// item->dimensions.wait_event_info = proc->wait_event_info; +// item->dimensions.queryId = pgws_proc_queryids[proc - ProcGlobal->allProcs]; +// funcctx->max_calls = 1; +// } +// else +// { +// /* pg_wait_sampling_current view */ +// int procCount = ProcGlobal->allProcCount, +// i, +// j = 0; +// +// params->items = (HistoryItem *) palloc0(sizeof(HistoryItem) * procCount); +// for (i = 0; i < procCount; i++) +// { +// PGPROC *proc = &ProcGlobal->allProcs[i]; +// +// if (!pgws_should_sample_proc(proc, +// ¶ms->items[j].dimensions.pid, +// ¶ms->items[j].dimensions.wait_event_info)) +// continue; +// +// params->items[j].dimensions.pid = proc->pid; +// params->items[j].dimensions.wait_event_info = proc->wait_event_info; +// params->items[j].dimensions.queryId = pgws_proc_queryids[i]; +// j++; +// } +// funcctx->max_calls = j; +// } +// +// LWLockRelease(ProcArrayLock); +// +// MemoryContextSwitchTo(oldcontext); +// } +// +// /* stuff done on every call of the function */ +// funcctx = SRF_PERCALL_SETUP(); +// params = (WaitCurrentContext *) funcctx->user_fctx; +// +// if (funcctx->call_cntr < funcctx->max_calls) +// { +// HeapTuple tuple; +// Datum values[4]; +// bool nulls[4]; +// const char *event_type, +// *event; +// HistoryItem *item; +// +// item = ¶ms->items[funcctx->call_cntr]; +// +// /* Make and return next tuple to caller */ +// MemSet(values, 0, sizeof(values)); +// MemSet(nulls, 0, sizeof(nulls)); +// +// event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); +// event = pgstat_get_wait_event(item->dimensions.wait_event_info); +// values[0] = Int32GetDatum(item->dimensions.pid); +// if (event_type) +// values[1] = PointerGetDatum(cstring_to_text(event_type)); +// else +// nulls[1] = true; +// if (event) +// values[2] = PointerGetDatum(cstring_to_text(event)); +// else +// nulls[2] = true; +// +// values[3] = UInt64GetDatum(item->dimensions.queryId); +// tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); +// +// SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); +// } +// else +// { +// /* nothing left */ +// SRF_RETURN_DONE(funcctx); +// } +//} static Datum GetBackendState(BackendState state, bool *is_null) @@ -847,8 +855,8 @@ get_beentry_by_procpid(int pid) /* Here beid is just index in localBackendStatusTable */ local_beentry = pgstat_fetch_stat_local_beentry(cur_be_idx); #endif - if (local_beentry->backendStatus.st_procpid == pid) - return &local_beentry->backendStatus; + if (local_beentry->backendStatus->st_procpid == pid) + return local_beentry->backendStatus; } return NULL; } @@ -890,7 +898,7 @@ fill_tuple_desc (TupleDesc tupdesc) } static void -fill_values_and_nulls(Datum *values, bool *nulls, SamplingDimensions dimensions, bool skip_mask) +fill_values_and_nulls(Datum *values, bool *nulls, SamplingDimensions dimensions, int dimensions_mask) { const char *event_type, *event, @@ -906,56 +914,59 @@ fill_values_and_nulls(Datum *values, bool *nulls, SamplingDimensions dimensions, proc_start = TimestampTzGetDatum(dimensions.proc_start); client_addr = get_backend_client_addr(dimensions.client_addr, &is_null_client_addr); - values[0] = Int32GetDatum(dimensions.pid); - if (event_type) + if (dimensions_mask & PGWS_DIMENSIONS_PID) + values[0] = Int32GetDatum(dimensions.pid); + else + values[0] = (Datum) 0; + if (event_type && (dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT_TYPE)) values[1] = PointerGetDatum(cstring_to_text(event_type)); else nulls[1] = true; - if (event) + if (event && (dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT)) values[2] = PointerGetDatum(cstring_to_text(event)); else nulls[2] = true; - if (pgws_profileQueries || skip_mask) + if (pgws_profileQueries || (dimensions_mask & PGWD_DIMENSIONS_QUERY_ID)) values[3] = UInt64GetDatum(dimensions.queryId); else values[3] = (Datum) 0; - if ((pgws_profile_dimensions & PGWS_DIMENSIONS_ROLE_ID) || skip_mask) + if (dimensions_mask & PGWS_DIMENSIONS_ROLE_ID) values[4] = ObjectIdGetDatum(dimensions.role_id); else nulls[4] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_DB_ID || skip_mask) + if (dimensions_mask & PGWS_DIMENSIONS_DB_ID) values[5] = ObjectIdGetDatum(dimensions.database_id); else nulls[5] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_PARALLEL_LEADER_PID || skip_mask) + if (dimensions_mask & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) values[6] = Int32GetDatum(dimensions.parallel_leader_pid); else nulls[6] = true; - if (pgws_profile_dimensions & PGWS_DIMENSIONS_IS_REGULAR_BE || skip_mask) + if (dimensions_mask & PGWS_DIMENSIONS_IS_REGULAR_BE) values[7] = BoolGetDatum(dimensions.is_regular_backend); else nulls[7] = true; - if (backend_type && ((pgws_profile_dimensions & PGWS_DIMENSIONS_BE_TYPE) || skip_mask)) + if (backend_type && (dimensions_mask & PGWS_DIMENSIONS_BE_TYPE)) values[8] = PointerGetDatum(cstring_to_text(backend_type)); else nulls[8] = true; - if (!is_null_be_state && ((pgws_profile_dimensions & PGWS_DIMENSIONS_BE_STATE) || skip_mask)) + if (!is_null_be_state && (dimensions_mask & PGWS_DIMENSIONS_BE_STATE)) values[9] = backend_state; else nulls[9] = true; - if ((pgws_profile_dimensions & PGWS_DIMENSIONS_BE_START_TIME) || skip_mask) + if (dimensions_mask & PGWS_DIMENSIONS_BE_START_TIME) values[10] = proc_start; else nulls[10] = true; - if (!is_null_client_addr && ((pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_ADDR) || skip_mask)) + if (!is_null_client_addr && (dimensions_mask & PGWS_DIMENSIONS_CLIENT_ADDR)) values[11] = client_addr; else nulls[11] = true; - if ((pgws_profile_dimensions & PGWS_DIMENSIONS_CLIENT_HOSTNAME) || skip_mask) + if (dimensions_mask & PGWS_DIMENSIONS_CLIENT_HOSTNAME) values[12] = PointerGetDatum(cstring_to_text(dimensions.client_hostname)); else nulls[12] = true; - if ((pgws_profile_dimensions & PGWS_DIMENSIONS_APPNAME) || skip_mask) + if (dimensions_mask & PGWS_DIMENSIONS_APPNAME) values[13] = PointerGetDatum(cstring_to_text(dimensions.appname)); else nulls[13] = true; @@ -995,7 +1006,7 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) /* pg_wait_sampling_get_current_extended(pid int4) function */ HistoryItem *item; PGPROC *proc; - PgBackendStatus *bestatus; + //PgBackendStatus *bestatus; not needed? proc = search_proc(PG_GETARG_UINT32(0)); @@ -1021,12 +1032,12 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) { PGPROC *proc = &ProcGlobal->allProcs[i]; - if (!pgws_should_sample_proc(&proc, + if (!pgws_should_sample_proc(proc, ¶ms->items[j].dimensions.pid, ¶ms->items[j].dimensions.wait_event_info)) continue; - fill_dimensions(¶ms->items[j]->dimensions, proc, proc->pid, + fill_dimensions(¶ms->items[j].dimensions, proc, proc->pid, proc->wait_event_info, pgws_proc_queryids[proc - ProcGlobal->allProcs], PGWS_DIMENSIONS_ALL); @@ -1063,7 +1074,7 @@ pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - fill_values_and_nulls(values, nulls, item->dimensions, true); + fill_values_and_nulls(values, nulls, item->dimensions, PGWS_DIMENSIONS_ALL); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); @@ -1179,100 +1190,132 @@ receive_array(SHMRequest request, Size item_size, Size *count) return result; } - -PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile); -Datum -pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) +static void * +deserialize_array(void *tmp_array, int count, Size real_item_size, bool is_history) { - Profile *profile; - FuncCallContext *funcctx; - - check_shmem(); + Pointer result; + int i; + int dimensions_mask = (is_history ? saved_history_dimensions : saved_profile_dimensions); + int serialized_size = get_serialized_size(saved_profile_dimensions, true); - if (SRF_IS_FIRSTCALL()) + result = palloc0(real_item_size * count); + for (i = 0; i < count; i++) { - MemoryContext oldcontext; - TupleDesc tupdesc; + SamplingDimensions tmp_dimensions; + char *cur_item; + TimestampTz *ts; + uint64 *count; - funcctx = SRF_FIRSTCALL_INIT(); - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + cur_item = (((char *) tmp_array) + i * serialized_size); + ts = (is_history ? palloc0(sizeof(TimestampTz)) : NULL); + count = (is_history ? NULL : palloc0(sizeof(uint64))); - /* Receive profile from shmq */ - profile = (Profile *) palloc0(sizeof(Profile)); - profile->items = (ProfileItem *) receive_array(PROFILE_REQUEST, - sizeof(ProfileItem), &profile->count); + deserialize_item(&tmp_dimensions, cur_item, dimensions_mask, ts, count); - funcctx->user_fctx = profile; - funcctx->max_calls = profile->count; + memcpy((result + i * real_item_size), &tmp_dimensions, sizeof(SamplingDimensions)); - /* Make tuple descriptor */ - tupdesc = CreateTemplateTupleDesc(5); - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 5, "count", - INT8OID, -1, 0); - funcctx->tuple_desc = BlessTupleDesc(tupdesc); - - MemoryContextSwitchTo(oldcontext); - } - - /* stuff done on every call of the function */ - funcctx = SRF_PERCALL_SETUP(); - - profile = (Profile *) funcctx->user_fctx; - - if (funcctx->call_cntr < funcctx->max_calls) - { - /* for each row */ - Datum values[5]; - bool nulls[5]; - HeapTuple tuple; - ProfileItem *item; - const char *event_type, - *event; - - item = &profile->items[funcctx->call_cntr]; - - MemSet(values, 0, sizeof(values)); - MemSet(nulls, 0, sizeof(nulls)); - - /* Make and return next tuple to caller */ - event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); - event = pgstat_get_wait_event(item->dimensions.wait_event_info); - values[0] = Int32GetDatum(item->dimensions.pid); - if (event_type) - values[1] = PointerGetDatum(cstring_to_text(event_type)); - else - nulls[1] = true; - if (event) - values[2] = PointerGetDatum(cstring_to_text(event)); + if (is_history) + memcpy((result + i * real_item_size + serialized_size), ts, sizeof(TimestampTz)); else - nulls[2] = true; - - if (pgws_profileQueries) - values[3] = UInt64GetDatum(item->dimensions.queryId); - else - values[3] = (Datum) 0; - - values[4] = UInt64GetDatum(item->count); - - tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); - - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); - } - else - { - /* nothing left */ - SRF_RETURN_DONE(funcctx); + memcpy((result + i * real_item_size + serialized_size), count, sizeof(uint64)); } + + return result; } +//PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile); +//Datum +//pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) +//{ +// Profile *profile; +// FuncCallContext *funcctx; +// +// check_shmem(); +// +// if (SRF_IS_FIRSTCALL()) +// { +// MemoryContext oldcontext; +// TupleDesc tupdesc; +// +// funcctx = SRF_FIRSTCALL_INIT(); +// oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); +// +// /* Receive profile from shmq */ +// profile = (Profile *) palloc0(sizeof(Profile)); +// profile->items = (ProfileItem *) receive_array(PROFILE_REQUEST, +// sizeof(ProfileItem), &profile->count); +// +// funcctx->user_fctx = profile; +// funcctx->max_calls = profile->count; +// +// /* Make tuple descriptor */ +// tupdesc = CreateTemplateTupleDesc(5); +// TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", +// INT4OID, -1, 0); +// TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", +// TEXTOID, -1, 0); +// TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", +// TEXTOID, -1, 0); +// TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", +// INT8OID, -1, 0); +// TupleDescInitEntry(tupdesc, (AttrNumber) 5, "count", +// INT8OID, -1, 0); +// funcctx->tuple_desc = BlessTupleDesc(tupdesc); +// +// MemoryContextSwitchTo(oldcontext); +// } +// +// /* stuff done on every call of the function */ +// funcctx = SRF_PERCALL_SETUP(); +// +// profile = (Profile *) funcctx->user_fctx; +// +// if (funcctx->call_cntr < funcctx->max_calls) +// { +// /* for each row */ +// Datum values[5]; +// bool nulls[5]; +// HeapTuple tuple; +// ProfileItem *item; +// const char *event_type, +// *event; +// +// item = &profile->items[funcctx->call_cntr]; +// +// MemSet(values, 0, sizeof(values)); +// MemSet(nulls, 0, sizeof(nulls)); +// +// /* Make and return next tuple to caller */ +// event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); +// event = pgstat_get_wait_event(item->dimensions.wait_event_info); +// values[0] = Int32GetDatum(item->dimensions.pid); +// if (event_type) +// values[1] = PointerGetDatum(cstring_to_text(event_type)); +// else +// nulls[1] = true; +// if (event) +// values[2] = PointerGetDatum(cstring_to_text(event)); +// else +// nulls[2] = true; +// +// if (pgws_profileQueries) +// values[3] = UInt64GetDatum(item->dimensions.queryId); +// else +// values[3] = (Datum) 0; +// +// values[4] = UInt64GetDatum(item->count); +// +// tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); +// +// SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); +// } +// else +// { +// /* nothing left */ +// SRF_RETURN_DONE(funcctx); +// } +//} + PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile_extended); Datum pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) @@ -1286,15 +1329,19 @@ pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) { MemoryContext oldcontext; TupleDesc tupdesc; + void *tmp_array; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* Receive profile from shmq */ profile = (Profile *) palloc0(sizeof(Profile)); - profile->items = (ProfileItem *) receive_array(PROFILE_REQUEST, - sizeof(ProfileItem), &profile->count); - + + tmp_array = receive_array(PROFILE_REQUEST, + get_serialized_size(saved_profile_dimensions, true), + &profile->count); + profile->items = (ProfileItem *) deserialize_array(tmp_array, profile->count, + sizeof(ProfileItem), false); funcctx->user_fctx = profile; funcctx->max_calls = profile->count; @@ -1327,7 +1374,7 @@ pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - fill_values_and_nulls(values, nulls, item->dimensions, false); + fill_values_and_nulls(values, nulls, item->dimensions, pgws_profile_dimensions); values[14] = UInt64GetDatum(item->count); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); @@ -1373,95 +1420,95 @@ pg_wait_sampling_reset_profile(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } -PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history); -Datum -pg_wait_sampling_get_history(PG_FUNCTION_ARGS) -{ - History *history; - FuncCallContext *funcctx; - - check_shmem(); - - if (SRF_IS_FIRSTCALL()) - { - MemoryContext oldcontext; - TupleDesc tupdesc; - - funcctx = SRF_FIRSTCALL_INIT(); - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - - /* Receive history from shmq */ - history = (History *) palloc0(sizeof(History)); - history->items = (HistoryItem *) receive_array(HISTORY_REQUEST, - sizeof(HistoryItem), &history->count); - - funcctx->user_fctx = history; - funcctx->max_calls = history->count; - - /* Make tuple descriptor */ - tupdesc = CreateTemplateTupleDesc(5); - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sample_ts", - TIMESTAMPTZOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "event", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 5, "queryid", - INT8OID, -1, 0); - funcctx->tuple_desc = BlessTupleDesc(tupdesc); - - MemoryContextSwitchTo(oldcontext); - } - - /* stuff done on every call of the function */ - funcctx = SRF_PERCALL_SETUP(); - - history = (History *) funcctx->user_fctx; - - if (history->index < history->count) - { - HeapTuple tuple; - HistoryItem *item; - Datum values[5]; - bool nulls[5]; - const char *event_type, - *event; - - item = &history->items[history->index]; - - /* Make and return next tuple to caller */ - MemSet(values, 0, sizeof(values)); - MemSet(nulls, 0, sizeof(nulls)); - - event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); - event = pgstat_get_wait_event(item->dimensions.wait_event_info); - values[0] = Int32GetDatum(item->dimensions.pid); - values[1] = TimestampTzGetDatum(item->ts); - if (event_type) - values[2] = PointerGetDatum(cstring_to_text(event_type)); - else - nulls[2] = true; - if (event) - values[3] = PointerGetDatum(cstring_to_text(event)); - else - nulls[3] = true; - - values[4] = UInt64GetDatum(item->dimensions.queryId); - tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); - - history->index++; - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); - } - else - { - /* nothing left */ - SRF_RETURN_DONE(funcctx); - } - - PG_RETURN_VOID(); -} +//PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history); +//Datum +//pg_wait_sampling_get_history(PG_FUNCTION_ARGS) +//{ +// History *history; +// FuncCallContext *funcctx; +// +// check_shmem(); +// +// if (SRF_IS_FIRSTCALL()) +// { +// MemoryContext oldcontext; +// TupleDesc tupdesc; +// +// funcctx = SRF_FIRSTCALL_INIT(); +// oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); +// +// /* Receive history from shmq */ +// history = (History *) palloc0(sizeof(History)); +// history->items = (HistoryItem *) receive_array(HISTORY_REQUEST, +// sizeof(HistoryItem), &history->count); +// +// funcctx->user_fctx = history; +// funcctx->max_calls = history->count; +// +// /* Make tuple descriptor */ +// tupdesc = CreateTemplateTupleDesc(5); +// TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", +// INT4OID, -1, 0); +// TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sample_ts", +// TIMESTAMPTZOID, -1, 0); +// TupleDescInitEntry(tupdesc, (AttrNumber) 3, "type", +// TEXTOID, -1, 0); +// TupleDescInitEntry(tupdesc, (AttrNumber) 4, "event", +// TEXTOID, -1, 0); +// TupleDescInitEntry(tupdesc, (AttrNumber) 5, "queryid", +// INT8OID, -1, 0); +// funcctx->tuple_desc = BlessTupleDesc(tupdesc); +// +// MemoryContextSwitchTo(oldcontext); +// } +// +// /* stuff done on every call of the function */ +// funcctx = SRF_PERCALL_SETUP(); +// +// history = (History *) funcctx->user_fctx; +// +// if (history->index < history->count) +// { +// HeapTuple tuple; +// HistoryItem *item; +// Datum values[5]; +// bool nulls[5]; +// const char *event_type, +// *event; +// +// item = &history->items[history->index]; +// +// /* Make and return next tuple to caller */ +// MemSet(values, 0, sizeof(values)); +// MemSet(nulls, 0, sizeof(nulls)); +// +// event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); +// event = pgstat_get_wait_event(item->dimensions.wait_event_info); +// values[0] = Int32GetDatum(item->dimensions.pid); +// values[1] = TimestampTzGetDatum(item->ts); +// if (event_type) +// values[2] = PointerGetDatum(cstring_to_text(event_type)); +// else +// nulls[2] = true; +// if (event) +// values[3] = PointerGetDatum(cstring_to_text(event)); +// else +// nulls[3] = true; +// +// values[4] = UInt64GetDatum(item->dimensions.queryId); +// tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); +// +// history->index++; +// SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); +// } +// else +// { +// /* nothing left */ +// SRF_RETURN_DONE(funcctx); +// } +// +// PG_RETURN_VOID(); +//} PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history_extended); Datum @@ -1469,6 +1516,7 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) { History *history; FuncCallContext *funcctx; + void *tmp_array; check_shmem(); @@ -1482,9 +1530,11 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) /* Receive history from shmq */ history = (History *) palloc0(sizeof(History)); - history->items = (HistoryItem *) receive_array(HISTORY_REQUEST, - sizeof(HistoryItem), &history->count); - + tmp_array = receive_array(PROFILE_REQUEST, + get_serialized_size(saved_history_dimensions, true), + &history->count); + history->items = (HistoryItem *) deserialize_array(tmp_array, history->count, + sizeof(HistoryItem), true); funcctx->user_fctx = history; funcctx->max_calls = history->count; @@ -1516,7 +1566,7 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - fill_values_and_nulls(values, nulls, item->dimensions, false); + fill_values_and_nulls(values, nulls, item->dimensions, pgws_history_dimensions); values[14] = TimestampTzGetDatum(item->ts); //TODO!!!!!!!!!!! tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); diff --git a/pg_wait_sampling.h b/pg_wait_sampling.h index ce7a721..d912a51 100644 --- a/pg_wait_sampling.h +++ b/pg_wait_sampling.h @@ -30,16 +30,20 @@ /* Values for sampling dimensions */ #define PGWS_DIMENSIONS_NONE 0 -#define PGWS_DIMENSIONS_ROLE_ID (1 << 0) -#define PGWS_DIMENSIONS_DB_ID (1 << 1) -#define PGWS_DIMENSIONS_PARALLEL_LEADER_PID (1 << 2) -#define PGWS_DIMENSIONS_IS_REGULAR_BE (1 << 3) -#define PGWS_DIMENSIONS_BE_TYPE (1 << 4) -#define PGWS_DIMENSIONS_BE_STATE (1 << 5) -#define PGWS_DIMENSIONS_BE_START_TIME (1 << 6) -#define PGWS_DIMENSIONS_CLIENT_ADDR (1 << 7) -#define PGWS_DIMENSIONS_CLIENT_HOSTNAME (1 << 8) -#define PGWS_DIMENSIONS_APPNAME (1 << 9) +#define PGWS_DIMENSIONS_PID (1 << 0) +#define PGWS_DIMENSIONS_WAIT_EVENT_TYPE (1 << 1) +#define PGWS_DIMENSIONS_WAIT_EVENT (1 << 2) +#define PGWD_DIMENSIONS_QUERY_ID (1 << 3) +#define PGWS_DIMENSIONS_ROLE_ID (1 << 4) +#define PGWS_DIMENSIONS_DB_ID (1 << 5) +#define PGWS_DIMENSIONS_PARALLEL_LEADER_PID (1 << 6) +#define PGWS_DIMENSIONS_IS_REGULAR_BE (1 << 7) +#define PGWS_DIMENSIONS_BE_TYPE (1 << 8) +#define PGWS_DIMENSIONS_BE_STATE (1 << 9) +#define PGWS_DIMENSIONS_BE_START_TIME (1 << 10) +#define PGWS_DIMENSIONS_CLIENT_ADDR (1 << 11) +#define PGWS_DIMENSIONS_CLIENT_HOSTNAME (1 << 12) +#define PGWS_DIMENSIONS_APPNAME (1 << 13) #define PGWS_DIMENSIONS_ALL ((int) ~0) /* ^ all 1 in binary */ @@ -111,6 +115,8 @@ extern int pgws_profilePeriod; extern bool pgws_profilePid; extern int pgws_profileQueries; extern bool pgws_sampleCpu; +extern int pgws_history_dimensions; +extern int pgws_profile_dimensions; /* pg_wait_sampling.c */ extern CollectorShmqHeader *pgws_collector_hdr; @@ -118,14 +124,17 @@ extern shm_mq *pgws_collector_mq; extern uint64 *pgws_proc_queryids; extern void pgws_init_lock_tag(LOCKTAG *tag, uint32 lock); extern bool pgws_should_sample_proc(PGPROC *proc, int *pid_p, uint32 *wait_event_info_p); -extern int pgws_history_dimensions; /* bit mask that is derived from GUC */ -extern int pgws_profile_dimensions; /* bit mask that is derived from GUC */ extern PgBackendStatus* get_beentry_by_procpid(int pid); /* collector.c */ +extern int saved_profile_dimensions; +extern int saved_history_dimensions; extern void fill_dimensions(SamplingDimensions *dimensions, PGPROC *proc, int pid, uint32 wait_event_info, uint64 queryId, - int dimensions_mask) + int dimensions_mask); +extern void deserialize_item(SamplingDimensions* dimensions, char* serialized_item, + int dimensions_mask, TimestampTz* ts, uint64* count); +extern int get_serialized_size(int dimensions_mask, bool need_last_field); extern void pgws_register_wait_collector(void); extern PGDLLEXPORT void pgws_collector_main(Datum main_arg); From 46af7da112389738efb7cc74cf3d7e6c0ece6c50 Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Mon, 23 Jun 2025 14:10:29 +0700 Subject: [PATCH 06/11] Fixing serialization --- collector.c | 110 ++++-- pg_wait_sampling--1.1--1.2.sql | 37 +- pg_wait_sampling.c | 673 +++++++++++++++++---------------- pg_wait_sampling.h | 2 + 4 files changed, 447 insertions(+), 375 deletions(-) diff --git a/collector.c b/collector.c index 180fcce..65a146b 100644 --- a/collector.c +++ b/collector.c @@ -40,7 +40,7 @@ PGWS_DIMENSIONS_APPNAME)) static volatile sig_atomic_t shutdown_requested = false; -int saved_profile_dimensions; //TODO should be initialized with the same value as GUC? +int saved_profile_dimensions; int saved_history_dimensions; static void handle_sigterm(SIGNAL_ARGS); @@ -72,12 +72,15 @@ pgws_register_wait_collector(void) static void alloc_history(History *observations, int count) { - observations->items = (HistoryItem *) palloc0(sizeof(HistoryItem) * count); + int serialized_size; + + saved_history_dimensions = pgws_history_dimensions; + serialized_size = get_serialized_size(saved_history_dimensions, true); + + observations->serialized_items = (char *) palloc0(serialized_size * count); observations->index = 0; observations->count = count; observations->wraparound = false; - - saved_history_dimensions = pgws_history_dimensions; } /* @@ -86,13 +89,17 @@ alloc_history(History *observations, int count) static void realloc_history(History *observations, int count) { - HistoryItem *newitems; + char *newitems; int copyCount, i, j; + int serialized_size; + + //saved_history_dimensions = pgws_history_dimensions; // TODO вроде как + serialized_size = get_serialized_size(saved_history_dimensions, true); /* Allocate new array for history */ - newitems = (HistoryItem *) palloc0(sizeof(HistoryItem) * count); + newitems = (char *) palloc0(serialized_size * count); /* Copy entries from old array to the new */ if (observations->wraparound) @@ -111,19 +118,19 @@ realloc_history(History *observations, int count) { if (j >= observations->count) j = 0; - memcpy(&newitems[i], &observations->items[j], sizeof(HistoryItem)); + memcpy((newitems + i * serialized_size), + (observations->serialized_items + j * serialized_size), + serialized_size); i++; j++; } /* Switch to new history array */ - pfree(observations->items); - observations->items = newitems; + pfree(observations->serialized_items); + observations->serialized_items = newitems; observations->index = copyCount; observations->count = count; observations->wraparound = false; - - saved_history_dimensions = pgws_history_dimensions; } static void @@ -140,10 +147,11 @@ handle_sigterm(SIGNAL_ARGS) /* * Get next item of history with rotation. */ -static HistoryItem * +static char * get_next_observation(History *observations) { - HistoryItem *result; + char *result; + int serialized_size = get_serialized_size(saved_history_dimensions, true); /* Check for wraparound */ if (observations->index >= observations->count) @@ -151,7 +159,7 @@ get_next_observation(History *observations) observations->index = 0; observations->wraparound = true; } - result = &observations->items[observations->index]; + result = &observations->serialized_items[observations->index * serialized_size]; observations->index++; return result; } @@ -413,8 +421,8 @@ serialize_item(SamplingDimensions dimensions, int dimensions_mask, } /* copy all the fields without ts/count */ - *serialized_key = palloc0(*serialized_size + 1); - strcpy(*serialized_key, dummy_array); + *serialized_key = palloc0(*serialized_size); + memcpy(*serialized_key, dummy_array, *serialized_size); if (is_history) { @@ -430,8 +438,8 @@ serialize_item(SamplingDimensions dimensions, int dimensions_mask, } /* copy everything */ - *serialized_item = palloc0(*serialized_size + 1); - strcpy(*serialized_item, dummy_array); + *serialized_item = palloc0(*serialized_size); + memcpy(*serialized_item, dummy_array, *serialized_size); } void @@ -570,17 +578,17 @@ probe_waits(History *observations, HTAB *profile_hash, LWLockAcquire(ProcArrayLock, LW_SHARED); for (i = 0; i < ProcGlobal->allProcCount; i++) { - HistoryItem item_history, - *observation; - ProfileItem item_profile; + //HistoryItem item_history, + // *observation; + //ProfileItem item_profile; PGPROC *proc = &ProcGlobal->allProcs[i]; int pid; uint32 wait_event_info; SamplingDimensions common_dimensions, history_dimensions, profile_dimensions; - int dimensions_mask_common = pgws_history_dimensions | - pgws_profile_dimensions; + int dimensions_mask_common = saved_history_dimensions | + saved_profile_dimensions; /* Check if we need to sample this process */ if (!pgws_should_sample_proc(proc, &pid, &wait_event_info)) @@ -598,21 +606,27 @@ probe_waits(History *observations, HTAB *profile_hash, copy_dimensions(&history_dimensions, &common_dimensions, - pgws_history_dimensions); + saved_history_dimensions); copy_dimensions(&profile_dimensions, &common_dimensions, - pgws_profile_dimensions); + saved_profile_dimensions); - item_history.ts = ts; - item_history.dimensions = history_dimensions; + //item_history.ts = ts; + //item_history.dimensions = history_dimensions; /* Write to the history if needed */ if (write_history) { - //TODO вот тут что-то сделать нужно??? потому что мы не запаковываем - //историю + char *serialized_key, + *serialized_item, + *observation; + int serialized_size = 0; + observation = get_next_observation(observations); - *observation = item_history; + serialize_item(history_dimensions, saved_history_dimensions, + &serialized_item, &serialized_key, &serialized_size, + ts, (uint64) 0, true); + memcpy(observation, serialized_item, serialized_size); } /* Write to the profile if needed */ @@ -626,9 +640,9 @@ probe_waits(History *observations, HTAB *profile_hash, *stored_item; if (!profile_pid) - item_profile.dimensions.pid = 0; + profile_dimensions.pid = 0; - serialize_item(item_profile.dimensions, saved_profile_dimensions, + serialize_item(profile_dimensions, saved_profile_dimensions, &serialized_item, &serialized_key, &serialized_size, (TimestampTz) 0, count, false); @@ -659,8 +673,9 @@ probe_waits(History *observations, HTAB *profile_hash, * Send waits history to shared memory queue. */ static void -send_history(History *observations, shm_mq_handle *mqh) +send_history(History *observations, shm_mq_handle *mqh) //TODO TODO TODO { + int serialized_size = get_serialized_size(saved_history_dimensions, true); Size count, i; shm_mq_result mq_result; @@ -679,11 +694,20 @@ send_history(History *observations, shm_mq_handle *mqh) "receiver of message queue has been detached"))); return; } + /* Send saved_dimensions next */ + mq_result = shm_mq_send_compat(mqh, sizeof(saved_history_dimensions), &saved_history_dimensions, false, true); + if (mq_result == SHM_MQ_DETACHED) + { + ereport(WARNING, + (errmsg("pg_wait_sampling collector: " + "receiver of message queue has been detached"))); + return; + } for (i = 0; i < count; i++) { mq_result = shm_mq_send_compat(mqh, - sizeof(HistoryItem), - &observations->items[i], + serialized_size, + (observations->serialized_items + i * serialized_size), false, true); if (mq_result == SHM_MQ_DETACHED) @@ -703,7 +727,8 @@ static void send_profile(HTAB *profile_hash, shm_mq_handle *mqh) { HASH_SEQ_STATUS scan_status; - ProfileItem *item; + char *serialized_item; + int serialized_size = get_serialized_size(saved_profile_dimensions, true); Size count = hash_get_num_entries(profile_hash); shm_mq_result mq_result; @@ -716,10 +741,19 @@ send_profile(HTAB *profile_hash, shm_mq_handle *mqh) "receiver of message queue has been detached"))); return; } + /* Send saved_dimensions next */ + mq_result = shm_mq_send_compat(mqh, sizeof(saved_profile_dimensions), &saved_profile_dimensions, false, true); + if (mq_result == SHM_MQ_DETACHED) + { + ereport(WARNING, + (errmsg("pg_wait_sampling collector: " + "receiver of message queue has been detached"))); + return; + } hash_seq_init(&scan_status, profile_hash); - while ((item = (ProfileItem *) hash_seq_search(&scan_status)) != NULL) + while ((serialized_item = (char *) hash_seq_search(&scan_status)) != NULL) { - mq_result = shm_mq_send_compat(mqh, sizeof(ProfileItem), item, false, + mq_result = shm_mq_send_compat(mqh, serialized_size, serialized_item, false, true); if (mq_result == SHM_MQ_DETACHED) { diff --git a/pg_wait_sampling--1.1--1.2.sql b/pg_wait_sampling--1.1--1.2.sql index e774f0f..e0e3337 100644 --- a/pg_wait_sampling--1.1--1.2.sql +++ b/pg_wait_sampling--1.1--1.2.sql @@ -3,9 +3,26 @@ -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pg_wait_sampling UPDATE TO 1.2" to load this file. \quit -DROP FUNCTION pg_wait_sampling_get_current CASCADE; -DROP FUNCTION pg_wait_sampling_get_profile CASCADE; -DROP FUNCTION pg_wait_sampling_get_history CASCADE; +--DROP FUNCTION pg_wait_sampling_get_current ( +-- pid int4, +-- OUT pid int4, +-- OUT event_type text, +-- OUT event text +--) CASCADE; +-- +--DROP FUNCTION pg_wait_sampling_get_history ( +-- OUT pid int4, +-- OUT ts timestamptz, +-- OUT event_type text, +-- OUT event text +--) CASCADE; +-- +--DROP FUNCTION pg_wait_sampling_get_profile ( +-- OUT pid int4, +-- OUT event_type text, +-- OUT event text, +-- OUT count bigint +--) CASCADE; CREATE FUNCTION pg_wait_sampling_get_current_extended ( pid int4, @@ -35,7 +52,6 @@ GRANT SELECT ON pg_wait_sampling_current TO PUBLIC; CREATE FUNCTION pg_wait_sampling_get_history_extended ( OUT pid int4, - OUT ts timestamptz, OUT event_type text, OUT event text, OUT queryid int8, @@ -48,7 +64,8 @@ CREATE FUNCTION pg_wait_sampling_get_history_extended ( OUT proc_start timestamptz, OUT client_addr text, OUT client_hostname text, - OUT appname text + OUT appname text, + OUT ts timestamptz ) RETURNS SETOF record AS 'MODULE_PATHNAME' @@ -85,9 +102,9 @@ CREATE VIEW pg_wait_sampling_profile_extended AS GRANT SELECT ON pg_wait_sampling_profile_extended TO PUBLIC; -CREATE VIEW pg_wait_sampling_profile AS - SELECT pid, event_type, event, queryid, SUM(count) FROM pg_wait_sampling_profile_extended - GROUP BY pid, event_type, event, queryid; - -GRANT SELECT ON pg_wait_sampling_profile TO PUBLIC; +--CREATE VIEW pg_wait_sampling_profile AS +-- SELECT pid, event_type, event, queryid, SUM(count) FROM pg_wait_sampling_profile_extended +-- GROUP BY pid, event_type, event, queryid; +-- +--GRANT SELECT ON pg_wait_sampling_profile TO PUBLIC; diff --git a/pg_wait_sampling.c b/pg_wait_sampling.c index 29d96ab..0ac0a8f 100644 --- a/pg_wait_sampling.c +++ b/pg_wait_sampling.c @@ -628,127 +628,128 @@ typedef struct TimestampTz ts; } WaitCurrentContext; -//PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current); -//Datum -//pg_wait_sampling_get_current(PG_FUNCTION_ARGS) -//{ -// FuncCallContext *funcctx; -// WaitCurrentContext *params; -// -// check_shmem(); -// -// if (SRF_IS_FIRSTCALL()) -// { -// MemoryContext oldcontext; -// TupleDesc tupdesc; -// -// funcctx = SRF_FIRSTCALL_INIT(); -// -// oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); -// params = (WaitCurrentContext *) palloc0(sizeof(WaitCurrentContext)); -// params->ts = GetCurrentTimestamp(); -// -// funcctx->user_fctx = params; -// tupdesc = CreateTemplateTupleDesc(4); -// TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", -// INT4OID, -1, 0); -// TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", -// TEXTOID, -1, 0); -// TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", -// TEXTOID, -1, 0); -// TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", -// INT8OID, -1, 0); -// -// funcctx->tuple_desc = BlessTupleDesc(tupdesc); -// -// LWLockAcquire(ProcArrayLock, LW_SHARED); -// -// if (!PG_ARGISNULL(0)) -// { -// /* pg_wait_sampling_get_current(pid int4) function */ -// HistoryItem *item; -// PGPROC *proc; -// -// proc = search_proc(PG_GETARG_UINT32(0)); -// params->items = (HistoryItem *) palloc0(sizeof(HistoryItem)); -// item = ¶ms->items[0]; -// item->dimensions.pid = proc->pid; -// item->dimensions.wait_event_info = proc->wait_event_info; -// item->dimensions.queryId = pgws_proc_queryids[proc - ProcGlobal->allProcs]; -// funcctx->max_calls = 1; -// } -// else -// { -// /* pg_wait_sampling_current view */ -// int procCount = ProcGlobal->allProcCount, -// i, -// j = 0; -// -// params->items = (HistoryItem *) palloc0(sizeof(HistoryItem) * procCount); -// for (i = 0; i < procCount; i++) -// { -// PGPROC *proc = &ProcGlobal->allProcs[i]; -// -// if (!pgws_should_sample_proc(proc, -// ¶ms->items[j].dimensions.pid, -// ¶ms->items[j].dimensions.wait_event_info)) -// continue; -// -// params->items[j].dimensions.pid = proc->pid; -// params->items[j].dimensions.wait_event_info = proc->wait_event_info; -// params->items[j].dimensions.queryId = pgws_proc_queryids[i]; -// j++; -// } -// funcctx->max_calls = j; -// } -// -// LWLockRelease(ProcArrayLock); -// -// MemoryContextSwitchTo(oldcontext); -// } -// -// /* stuff done on every call of the function */ -// funcctx = SRF_PERCALL_SETUP(); -// params = (WaitCurrentContext *) funcctx->user_fctx; -// -// if (funcctx->call_cntr < funcctx->max_calls) -// { -// HeapTuple tuple; -// Datum values[4]; -// bool nulls[4]; -// const char *event_type, -// *event; -// HistoryItem *item; -// -// item = ¶ms->items[funcctx->call_cntr]; -// -// /* Make and return next tuple to caller */ -// MemSet(values, 0, sizeof(values)); -// MemSet(nulls, 0, sizeof(nulls)); -// -// event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); -// event = pgstat_get_wait_event(item->dimensions.wait_event_info); -// values[0] = Int32GetDatum(item->dimensions.pid); -// if (event_type) -// values[1] = PointerGetDatum(cstring_to_text(event_type)); -// else -// nulls[1] = true; -// if (event) -// values[2] = PointerGetDatum(cstring_to_text(event)); -// else -// nulls[2] = true; -// -// values[3] = UInt64GetDatum(item->dimensions.queryId); -// tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); -// -// SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); -// } -// else -// { -// /* nothing left */ -// SRF_RETURN_DONE(funcctx); -// } -//} +//TODO OBSOLETE +PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current); +Datum +pg_wait_sampling_get_current(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + WaitCurrentContext *params; + + check_shmem(); + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + params = (WaitCurrentContext *) palloc0(sizeof(WaitCurrentContext)); + params->ts = GetCurrentTimestamp(); + + funcctx->user_fctx = params; + tupdesc = CreateTemplateTupleDesc(4); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", + INT8OID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + LWLockAcquire(ProcArrayLock, LW_SHARED); + + if (!PG_ARGISNULL(0)) + { + /* pg_wait_sampling_get_current(pid int4) function */ + HistoryItem *item; + PGPROC *proc; + + proc = search_proc(PG_GETARG_UINT32(0)); + params->items = (HistoryItem *) palloc0(sizeof(HistoryItem)); + item = ¶ms->items[0]; + item->dimensions.pid = proc->pid; + item->dimensions.wait_event_info = proc->wait_event_info; + item->dimensions.queryId = pgws_proc_queryids[proc - ProcGlobal->allProcs]; + funcctx->max_calls = 1; + } + else + { + /* pg_wait_sampling_current view */ + int procCount = ProcGlobal->allProcCount, + i, + j = 0; + + params->items = (HistoryItem *) palloc0(sizeof(HistoryItem) * procCount); + for (i = 0; i < procCount; i++) + { + PGPROC *proc = &ProcGlobal->allProcs[i]; + + if (!pgws_should_sample_proc(proc, + ¶ms->items[j].dimensions.pid, + ¶ms->items[j].dimensions.wait_event_info)) + continue; + + params->items[j].dimensions.pid = proc->pid; + params->items[j].dimensions.wait_event_info = proc->wait_event_info; + params->items[j].dimensions.queryId = pgws_proc_queryids[i]; + j++; + } + funcctx->max_calls = j; + } + + LWLockRelease(ProcArrayLock); + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + params = (WaitCurrentContext *) funcctx->user_fctx; + + if (funcctx->call_cntr < funcctx->max_calls) + { + HeapTuple tuple; + Datum values[4]; + bool nulls[4]; + const char *event_type, + *event; + HistoryItem *item; + + item = ¶ms->items[funcctx->call_cntr]; + + /* Make and return next tuple to caller */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls)); + + event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); + event = pgstat_get_wait_event(item->dimensions.wait_event_info); + values[0] = Int32GetDatum(item->dimensions.pid); + if (event_type) + values[1] = PointerGetDatum(cstring_to_text(event_type)); + else + nulls[1] = true; + if (event) + values[2] = PointerGetDatum(cstring_to_text(event)); + else + nulls[2] = true; + + values[3] = UInt64GetDatum(item->dimensions.queryId); + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + else + { + /* nothing left */ + SRF_RETURN_DONE(funcctx); + } +} static Datum GetBackendState(BackendState state, bool *is_null) @@ -1106,7 +1107,7 @@ pgws_init_lock_tag(LOCKTAG *tag, uint32 lock) /* Get array (history or profile data) from shared memory */ static void * -receive_array(SHMRequest request, Size item_size, Size *count) +receive_array(SHMRequest request, Size *item_size, Size *count, int *dimensions_mask) { LOCKTAG collectorTag; shm_mq_result res; @@ -1168,17 +1169,25 @@ receive_array(SHMRequest request, Size item_size, Size *count) memcpy(count, data, sizeof(*count)); - result = palloc(item_size * (*count)); + res = shm_mq_receive(recv_mqh, &len, &data, false); + if (res != SHM_MQ_SUCCESS || len != sizeof(*dimensions_mask)) + elog(ERROR, "error reading mq"); + + memcpy(dimensions_mask, data, sizeof(*dimensions_mask)); + + *item_size = get_serialized_size(*dimensions_mask, true); + + result = palloc(*item_size * (*count)); ptr = result; for (i = 0; i < *count; i++) { res = shm_mq_receive(recv_mqh, &len, &data, false); - if (res != SHM_MQ_SUCCESS || len != item_size) + if (res != SHM_MQ_SUCCESS || len != *item_size) elog(ERROR, "error reading mq"); - memcpy(ptr, data, item_size); - ptr += item_size; + memcpy(ptr, data, *item_size); + ptr += *item_size; } } PG_END_ENSURE_ERROR_CLEANUP(pgws_cleanup_callback, 0); @@ -1191,20 +1200,24 @@ receive_array(SHMRequest request, Size item_size, Size *count) } static void * -deserialize_array(void *tmp_array, int count, Size real_item_size, bool is_history) +deserialize_array(void *tmp_array, int count, bool is_history) { - Pointer result; + Pointer result, + ptr; int i; int dimensions_mask = (is_history ? saved_history_dimensions : saved_profile_dimensions); - int serialized_size = get_serialized_size(saved_profile_dimensions, true); + int serialized_size = get_serialized_size(dimensions_mask, true); + + result = palloc0((is_history ? sizeof(HistoryItem) : sizeof(ProfileItem)) * count); + ptr = result; - result = palloc0(real_item_size * count); for (i = 0; i < count; i++) { SamplingDimensions tmp_dimensions; char *cur_item; TimestampTz *ts; uint64 *count; + int ts_count_size = sizeof(uint64); /* is 8 bytes anyway */ cur_item = (((char *) tmp_array) + i * serialized_size); ts = (is_history ? palloc0(sizeof(TimestampTz)) : NULL); @@ -1212,109 +1225,116 @@ deserialize_array(void *tmp_array, int count, Size real_item_size, bool is_histo deserialize_item(&tmp_dimensions, cur_item, dimensions_mask, ts, count); - memcpy((result + i * real_item_size), &tmp_dimensions, sizeof(SamplingDimensions)); - + memcpy(ptr, &tmp_dimensions, sizeof(SamplingDimensions)); + ptr += sizeof(SamplingDimensions); if (is_history) - memcpy((result + i * real_item_size + serialized_size), ts, sizeof(TimestampTz)); + { + memcpy(ptr, ts, ts_count_size); + ptr += sizeof(TimestampTz); + } else - memcpy((result + i * real_item_size + serialized_size), count, sizeof(uint64)); + { + memcpy(ptr, count, ts_count_size); + ptr += sizeof(uint64); + } } return result; } -//PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile); -//Datum -//pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) -//{ -// Profile *profile; -// FuncCallContext *funcctx; -// -// check_shmem(); -// -// if (SRF_IS_FIRSTCALL()) -// { -// MemoryContext oldcontext; -// TupleDesc tupdesc; -// -// funcctx = SRF_FIRSTCALL_INIT(); -// oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); -// -// /* Receive profile from shmq */ -// profile = (Profile *) palloc0(sizeof(Profile)); -// profile->items = (ProfileItem *) receive_array(PROFILE_REQUEST, -// sizeof(ProfileItem), &profile->count); -// -// funcctx->user_fctx = profile; -// funcctx->max_calls = profile->count; -// -// /* Make tuple descriptor */ -// tupdesc = CreateTemplateTupleDesc(5); -// TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", -// INT4OID, -1, 0); -// TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", -// TEXTOID, -1, 0); -// TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", -// TEXTOID, -1, 0); -// TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", -// INT8OID, -1, 0); -// TupleDescInitEntry(tupdesc, (AttrNumber) 5, "count", -// INT8OID, -1, 0); -// funcctx->tuple_desc = BlessTupleDesc(tupdesc); -// -// MemoryContextSwitchTo(oldcontext); -// } -// -// /* stuff done on every call of the function */ -// funcctx = SRF_PERCALL_SETUP(); -// -// profile = (Profile *) funcctx->user_fctx; -// -// if (funcctx->call_cntr < funcctx->max_calls) -// { -// /* for each row */ -// Datum values[5]; -// bool nulls[5]; -// HeapTuple tuple; -// ProfileItem *item; -// const char *event_type, -// *event; -// -// item = &profile->items[funcctx->call_cntr]; -// -// MemSet(values, 0, sizeof(values)); -// MemSet(nulls, 0, sizeof(nulls)); -// -// /* Make and return next tuple to caller */ -// event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); -// event = pgstat_get_wait_event(item->dimensions.wait_event_info); -// values[0] = Int32GetDatum(item->dimensions.pid); -// if (event_type) -// values[1] = PointerGetDatum(cstring_to_text(event_type)); -// else -// nulls[1] = true; -// if (event) -// values[2] = PointerGetDatum(cstring_to_text(event)); -// else -// nulls[2] = true; -// -// if (pgws_profileQueries) -// values[3] = UInt64GetDatum(item->dimensions.queryId); -// else -// values[3] = (Datum) 0; -// -// values[4] = UInt64GetDatum(item->count); -// -// tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); -// -// SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); -// } -// else -// { -// /* nothing left */ -// SRF_RETURN_DONE(funcctx); -// } -//} +//TODO OBSOLETE +PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile); +Datum +pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) +{ + Profile *profile; + FuncCallContext *funcctx; + + check_shmem(); + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* Receive profile from shmq */ + profile = (Profile *) palloc0(sizeof(Profile)); + //profile->items = (ProfileItem *) receive_array(PROFILE_REQUEST, + // sizeof(ProfileItem), &profile->count); + + funcctx->user_fctx = profile; + funcctx->max_calls = profile->count; + + /* Make tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(5); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "count", + INT8OID, -1, 0); + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + profile = (Profile *) funcctx->user_fctx; + + if (funcctx->call_cntr < funcctx->max_calls) + { + /* for each row */ + Datum values[5]; + bool nulls[5]; + HeapTuple tuple; + ProfileItem *item; + const char *event_type, + *event; + + item = &profile->items[funcctx->call_cntr]; + + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls)); + + /* Make and return next tuple to caller */ + event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); + event = pgstat_get_wait_event(item->dimensions.wait_event_info); + values[0] = Int32GetDatum(item->dimensions.pid); + if (event_type) + values[1] = PointerGetDatum(cstring_to_text(event_type)); + else + nulls[1] = true; + if (event) + values[2] = PointerGetDatum(cstring_to_text(event)); + else + nulls[2] = true; + + if (pgws_profileQueries) + values[3] = UInt64GetDatum(item->dimensions.queryId); + else + values[3] = (Datum) 0; + + values[4] = UInt64GetDatum(item->count); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + else + { + /* nothing left */ + SRF_RETURN_DONE(funcctx); + } +} PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile_extended); Datum @@ -1330,6 +1350,7 @@ pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) MemoryContext oldcontext; TupleDesc tupdesc; void *tmp_array; + Size serialized_size; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); @@ -1337,11 +1358,9 @@ pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) /* Receive profile from shmq */ profile = (Profile *) palloc0(sizeof(Profile)); - tmp_array = receive_array(PROFILE_REQUEST, - get_serialized_size(saved_profile_dimensions, true), - &profile->count); - profile->items = (ProfileItem *) deserialize_array(tmp_array, profile->count, - sizeof(ProfileItem), false); + tmp_array = receive_array(PROFILE_REQUEST, &serialized_size, + &profile->count, &saved_profile_dimensions); + profile->items = (ProfileItem *) deserialize_array(tmp_array, profile->count, false); funcctx->user_fctx = profile; funcctx->max_calls = profile->count; @@ -1420,95 +1439,96 @@ pg_wait_sampling_reset_profile(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } -//PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history); -//Datum -//pg_wait_sampling_get_history(PG_FUNCTION_ARGS) -//{ -// History *history; -// FuncCallContext *funcctx; -// -// check_shmem(); -// -// if (SRF_IS_FIRSTCALL()) -// { -// MemoryContext oldcontext; -// TupleDesc tupdesc; -// -// funcctx = SRF_FIRSTCALL_INIT(); -// oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); -// -// /* Receive history from shmq */ -// history = (History *) palloc0(sizeof(History)); -// history->items = (HistoryItem *) receive_array(HISTORY_REQUEST, -// sizeof(HistoryItem), &history->count); -// -// funcctx->user_fctx = history; -// funcctx->max_calls = history->count; -// -// /* Make tuple descriptor */ -// tupdesc = CreateTemplateTupleDesc(5); -// TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", -// INT4OID, -1, 0); -// TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sample_ts", -// TIMESTAMPTZOID, -1, 0); -// TupleDescInitEntry(tupdesc, (AttrNumber) 3, "type", -// TEXTOID, -1, 0); -// TupleDescInitEntry(tupdesc, (AttrNumber) 4, "event", -// TEXTOID, -1, 0); -// TupleDescInitEntry(tupdesc, (AttrNumber) 5, "queryid", -// INT8OID, -1, 0); -// funcctx->tuple_desc = BlessTupleDesc(tupdesc); -// -// MemoryContextSwitchTo(oldcontext); -// } -// -// /* stuff done on every call of the function */ -// funcctx = SRF_PERCALL_SETUP(); -// -// history = (History *) funcctx->user_fctx; -// -// if (history->index < history->count) -// { -// HeapTuple tuple; -// HistoryItem *item; -// Datum values[5]; -// bool nulls[5]; -// const char *event_type, -// *event; -// -// item = &history->items[history->index]; -// -// /* Make and return next tuple to caller */ -// MemSet(values, 0, sizeof(values)); -// MemSet(nulls, 0, sizeof(nulls)); -// -// event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); -// event = pgstat_get_wait_event(item->dimensions.wait_event_info); -// values[0] = Int32GetDatum(item->dimensions.pid); -// values[1] = TimestampTzGetDatum(item->ts); -// if (event_type) -// values[2] = PointerGetDatum(cstring_to_text(event_type)); -// else -// nulls[2] = true; -// if (event) -// values[3] = PointerGetDatum(cstring_to_text(event)); -// else -// nulls[3] = true; -// -// values[4] = UInt64GetDatum(item->dimensions.queryId); -// tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); -// -// history->index++; -// SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); -// } -// else -// { -// /* nothing left */ -// SRF_RETURN_DONE(funcctx); -// } -// -// PG_RETURN_VOID(); -//} +//TODO OBSOLETE +PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history); +Datum +pg_wait_sampling_get_history(PG_FUNCTION_ARGS) +{ + History *history; + FuncCallContext *funcctx; + + check_shmem(); + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* Receive history from shmq */ + history = (History *) palloc0(sizeof(History)); + //history->items = (HistoryItem *) receive_array(HISTORY_REQUEST, + // sizeof(HistoryItem), &history->count, &saved_history_dimensions); + + funcctx->user_fctx = history; + funcctx->max_calls = history->count; + + /* Make tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(5); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sample_ts", + TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "event", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "queryid", + INT8OID, -1, 0); + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + history = (History *) funcctx->user_fctx; + + if (history->index < history->count) + { + //HeapTuple tuple; + //HistoryItem *item; + //Datum values[5]; + //bool nulls[5]; + //const char *event_type, + // *event; + // + //item = &history->items[history->index]; + // + ///* Make and return next tuple to caller */ + //MemSet(values, 0, sizeof(values)); + //MemSet(nulls, 0, sizeof(nulls)); + // + //event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); + //event = pgstat_get_wait_event(item->dimensions.wait_event_info); + //values[0] = Int32GetDatum(item->dimensions.pid); + //values[1] = TimestampTzGetDatum(item->ts); + //if (event_type) + // values[2] = PointerGetDatum(cstring_to_text(event_type)); + //else + // nulls[2] = true; + //if (event) + // values[3] = PointerGetDatum(cstring_to_text(event)); + //else + // nulls[3] = true; + // + //values[4] = UInt64GetDatum(item->dimensions.queryId); + //tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + // + //history->index++; + //SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + else + { + /* nothing left */ + SRF_RETURN_DONE(funcctx); + } + + PG_RETURN_VOID(); +} PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history_extended); Datum @@ -1524,24 +1544,23 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) { MemoryContext oldcontext; TupleDesc tupdesc; + Size serialized_size; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* Receive history from shmq */ history = (History *) palloc0(sizeof(History)); - tmp_array = receive_array(PROFILE_REQUEST, - get_serialized_size(saved_history_dimensions, true), - &history->count); - history->items = (HistoryItem *) deserialize_array(tmp_array, history->count, - sizeof(HistoryItem), true); + tmp_array = receive_array(HISTORY_REQUEST, &serialized_size, + &history->count, &saved_history_dimensions); + history->items = (HistoryItem *) deserialize_array(tmp_array, history->count, true); funcctx->user_fctx = history; funcctx->max_calls = history->count; /* Make tuple descriptor */ tupdesc = CreateTemplateTupleDesc(15); fill_tuple_desc (tupdesc); - TupleDescInitEntry(tupdesc, (AttrNumber) 15, "sample_ts", //TODO we have moved this to the end to have it more in line with current and profile; debatable; maybe move it to first place? + TupleDescInitEntry(tupdesc, (AttrNumber) 15, "sample_ts", //XXX we have moved this to the end to have it more in line with current and profile; debatable; maybe move it to first place? TIMESTAMPTZOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); @@ -1567,7 +1586,7 @@ pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) MemSet(nulls, 0, sizeof(nulls)); fill_values_and_nulls(values, nulls, item->dimensions, pgws_history_dimensions); - values[14] = TimestampTzGetDatum(item->ts); //TODO!!!!!!!!!!! + values[14] = TimestampTzGetDatum(item->ts); //XXX same as above tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); diff --git a/pg_wait_sampling.h b/pg_wait_sampling.h index d912a51..c9b3b4a 100644 --- a/pg_wait_sampling.h +++ b/pg_wait_sampling.h @@ -91,6 +91,8 @@ typedef struct bool wraparound; Size index; Size count; + char *serialized_items; + /* used only in pg_wait_sampling.c */ HistoryItem *items; } History; From 64efec7aeab54718643aa99e23ad3ad6013ab432 Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Tue, 24 Jun 2025 13:21:33 +0700 Subject: [PATCH 07/11] Add history reset --- collector.c | 6 ++++++ pg_wait_sampling--1.1--1.2.sql | 8 ++++++++ pg_wait_sampling.c | 28 ++++++++++++++++++++++++++++ pg_wait_sampling.h | 1 + 4 files changed, 43 insertions(+) diff --git a/collector.c b/collector.c index 65a146b..9e6afad 100644 --- a/collector.c +++ b/collector.c @@ -961,6 +961,12 @@ pgws_collector_main(Datum main_arg) } shm_mq_detach(mqh); } + else if (request == HISTORY_RESET) + { + /* Reset history */ + pfree(observations.items); + alloc_history(&observations, pgws_historySize); + } else if (request == PROFILE_RESET) { /* Reset profile hash */ diff --git a/pg_wait_sampling--1.1--1.2.sql b/pg_wait_sampling--1.1--1.2.sql index e0e3337..073104d 100644 --- a/pg_wait_sampling--1.1--1.2.sql +++ b/pg_wait_sampling--1.1--1.2.sql @@ -102,6 +102,14 @@ CREATE VIEW pg_wait_sampling_profile_extended AS GRANT SELECT ON pg_wait_sampling_profile_extended TO PUBLIC; +CREATE FUNCTION pg_wait_sampling_reset_history() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C VOLATILE STRICT; + +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_wait_sampling_reset_history() FROM PUBLIC; + --CREATE VIEW pg_wait_sampling_profile AS -- SELECT pid, event_type, event, queryid, SUM(count) FROM pg_wait_sampling_profile_extended -- GROUP BY pid, event_type, event, queryid; diff --git a/pg_wait_sampling.c b/pg_wait_sampling.c index 0ac0a8f..7b2f823 100644 --- a/pg_wait_sampling.c +++ b/pg_wait_sampling.c @@ -1439,6 +1439,34 @@ pg_wait_sampling_reset_profile(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +PG_FUNCTION_INFO_V1(pg_wait_sampling_reset_history); +Datum +pg_wait_sampling_reset_history(PG_FUNCTION_ARGS) +{ + LOCKTAG collectorTag; + + check_shmem(); + + pgws_init_lock_tag(&queueTag, PGWS_QUEUE_LOCK); + + LockAcquire(&queueTag, ExclusiveLock, false, false); + + pgws_init_lock_tag(&collectorTag, PGWS_COLLECTOR_LOCK); + LockAcquire(&collectorTag, ExclusiveLock, false, false); + pgws_collector_hdr->request = HISTORY_RESET; + LockRelease(&collectorTag, ExclusiveLock, false); + + if (!pgws_collector_hdr->latch) + ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("pg_wait_sampling collector wasn't started"))); + + SetLatch(pgws_collector_hdr->latch); + + LockRelease(&queueTag, ExclusiveLock, false); + + PG_RETURN_VOID(); +} + //TODO OBSOLETE PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history); Datum diff --git a/pg_wait_sampling.h b/pg_wait_sampling.h index c9b3b4a..3b84b60 100644 --- a/pg_wait_sampling.h +++ b/pg_wait_sampling.h @@ -100,6 +100,7 @@ typedef enum { NO_REQUEST, HISTORY_REQUEST, + HISTORY_RESET, PROFILE_REQUEST, PROFILE_RESET } SHMRequest; From f6a2203ed1b9643a07343424942c8a2513aba2c9 Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Tue, 24 Jun 2025 18:00:01 +0700 Subject: [PATCH 08/11] remove old functions for good --- pg_wait_sampling--1.1--1.2.sql | 69 ++++---- pg_wait_sampling.c | 312 +-------------------------------- 2 files changed, 33 insertions(+), 348 deletions(-) diff --git a/pg_wait_sampling--1.1--1.2.sql b/pg_wait_sampling--1.1--1.2.sql index 073104d..8653d87 100644 --- a/pg_wait_sampling--1.1--1.2.sql +++ b/pg_wait_sampling--1.1--1.2.sql @@ -3,28 +3,28 @@ -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pg_wait_sampling UPDATE TO 1.2" to load this file. \quit ---DROP FUNCTION pg_wait_sampling_get_current ( --- pid int4, --- OUT pid int4, --- OUT event_type text, --- OUT event text ---) CASCADE; --- ---DROP FUNCTION pg_wait_sampling_get_history ( --- OUT pid int4, --- OUT ts timestamptz, --- OUT event_type text, --- OUT event text ---) CASCADE; --- ---DROP FUNCTION pg_wait_sampling_get_profile ( --- OUT pid int4, --- OUT event_type text, --- OUT event text, --- OUT count bigint ---) CASCADE; +DROP FUNCTION pg_wait_sampling_get_current ( + pid int4, + OUT pid int4, + OUT event_type text, + OUT event text +) CASCADE; + +DROP FUNCTION pg_wait_sampling_get_history ( + OUT pid int4, + OUT ts timestamptz, + OUT event_type text, + OUT event text +) CASCADE; -CREATE FUNCTION pg_wait_sampling_get_current_extended ( +DROP FUNCTION pg_wait_sampling_get_profile ( + OUT pid int4, + OUT event_type text, + OUT event text, + OUT count bigint +) CASCADE; + +CREATE FUNCTION pg_wait_sampling_get_current ( pid int4, OUT pid int4, OUT event_type text, @@ -45,12 +45,12 @@ RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE CALLED ON NULL INPUT; -CREATE VIEW pg_wait_sampling_current_extended AS - SELECT * FROM pg_wait_sampling_get_current_extended(NULL::integer); +CREATE VIEW pg_wait_sampling_current AS + SELECT * FROM pg_wait_sampling_get_current(NULL::integer); GRANT SELECT ON pg_wait_sampling_current TO PUBLIC; -CREATE FUNCTION pg_wait_sampling_get_history_extended ( +CREATE FUNCTION pg_wait_sampling_get_history ( OUT pid int4, OUT event_type text, OUT event text, @@ -71,12 +71,12 @@ RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE STRICT; -CREATE VIEW pg_wait_sampling_history_extended AS - SELECT * FROM pg_wait_sampling_get_history_extended(); +CREATE VIEW pg_wait_sampling_history AS + SELECT * FROM pg_wait_sampling_get_history(); -GRANT SELECT ON pg_wait_sampling_history_extended TO PUBLIC; +GRANT SELECT ON pg_wait_sampling_history TO PUBLIC; -CREATE FUNCTION pg_wait_sampling_get_profile_extended ( +CREATE FUNCTION pg_wait_sampling_get_profile ( OUT pid int4, OUT event_type text, OUT event text, @@ -97,10 +97,10 @@ RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE STRICT; -CREATE VIEW pg_wait_sampling_profile_extended AS - SELECT * FROM pg_wait_sampling_get_profile_extended(); +CREATE VIEW pg_wait_sampling_profile AS + SELECT * FROM pg_wait_sampling_get_profile(); -GRANT SELECT ON pg_wait_sampling_profile_extended TO PUBLIC; +GRANT SELECT ON pg_wait_sampling_profile TO PUBLIC; CREATE FUNCTION pg_wait_sampling_reset_history() RETURNS void @@ -109,10 +109,3 @@ LANGUAGE C VOLATILE STRICT; -- Don't want this to be available to non-superusers. REVOKE ALL ON FUNCTION pg_wait_sampling_reset_history() FROM PUBLIC; - ---CREATE VIEW pg_wait_sampling_profile AS --- SELECT pid, event_type, event, queryid, SUM(count) FROM pg_wait_sampling_profile_extended --- GROUP BY pid, event_type, event, queryid; --- ---GRANT SELECT ON pg_wait_sampling_profile TO PUBLIC; - diff --git a/pg_wait_sampling.c b/pg_wait_sampling.c index 7b2f823..2d2b8d0 100644 --- a/pg_wait_sampling.c +++ b/pg_wait_sampling.c @@ -628,129 +628,6 @@ typedef struct TimestampTz ts; } WaitCurrentContext; -//TODO OBSOLETE -PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current); -Datum -pg_wait_sampling_get_current(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - WaitCurrentContext *params; - - check_shmem(); - - if (SRF_IS_FIRSTCALL()) - { - MemoryContext oldcontext; - TupleDesc tupdesc; - - funcctx = SRF_FIRSTCALL_INIT(); - - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - params = (WaitCurrentContext *) palloc0(sizeof(WaitCurrentContext)); - params->ts = GetCurrentTimestamp(); - - funcctx->user_fctx = params; - tupdesc = CreateTemplateTupleDesc(4); - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", - INT8OID, -1, 0); - - funcctx->tuple_desc = BlessTupleDesc(tupdesc); - - LWLockAcquire(ProcArrayLock, LW_SHARED); - - if (!PG_ARGISNULL(0)) - { - /* pg_wait_sampling_get_current(pid int4) function */ - HistoryItem *item; - PGPROC *proc; - - proc = search_proc(PG_GETARG_UINT32(0)); - params->items = (HistoryItem *) palloc0(sizeof(HistoryItem)); - item = ¶ms->items[0]; - item->dimensions.pid = proc->pid; - item->dimensions.wait_event_info = proc->wait_event_info; - item->dimensions.queryId = pgws_proc_queryids[proc - ProcGlobal->allProcs]; - funcctx->max_calls = 1; - } - else - { - /* pg_wait_sampling_current view */ - int procCount = ProcGlobal->allProcCount, - i, - j = 0; - - params->items = (HistoryItem *) palloc0(sizeof(HistoryItem) * procCount); - for (i = 0; i < procCount; i++) - { - PGPROC *proc = &ProcGlobal->allProcs[i]; - - if (!pgws_should_sample_proc(proc, - ¶ms->items[j].dimensions.pid, - ¶ms->items[j].dimensions.wait_event_info)) - continue; - - params->items[j].dimensions.pid = proc->pid; - params->items[j].dimensions.wait_event_info = proc->wait_event_info; - params->items[j].dimensions.queryId = pgws_proc_queryids[i]; - j++; - } - funcctx->max_calls = j; - } - - LWLockRelease(ProcArrayLock); - - MemoryContextSwitchTo(oldcontext); - } - - /* stuff done on every call of the function */ - funcctx = SRF_PERCALL_SETUP(); - params = (WaitCurrentContext *) funcctx->user_fctx; - - if (funcctx->call_cntr < funcctx->max_calls) - { - HeapTuple tuple; - Datum values[4]; - bool nulls[4]; - const char *event_type, - *event; - HistoryItem *item; - - item = ¶ms->items[funcctx->call_cntr]; - - /* Make and return next tuple to caller */ - MemSet(values, 0, sizeof(values)); - MemSet(nulls, 0, sizeof(nulls)); - - event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); - event = pgstat_get_wait_event(item->dimensions.wait_event_info); - values[0] = Int32GetDatum(item->dimensions.pid); - if (event_type) - values[1] = PointerGetDatum(cstring_to_text(event_type)); - else - nulls[1] = true; - if (event) - values[2] = PointerGetDatum(cstring_to_text(event)); - else - nulls[2] = true; - - values[3] = UInt64GetDatum(item->dimensions.queryId); - tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); - - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); - } - else - { - /* nothing left */ - SRF_RETURN_DONE(funcctx); - } -} - static Datum GetBackendState(BackendState state, bool *is_null) { @@ -973,9 +850,9 @@ fill_values_and_nulls(Datum *values, bool *nulls, SamplingDimensions dimensions, nulls[13] = true; } -PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current_extended); +PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current); Datum -pg_wait_sampling_get_current_extended(PG_FUNCTION_ARGS) +pg_wait_sampling_get_current(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; WaitCurrentContext *params; @@ -1242,7 +1119,6 @@ deserialize_array(void *tmp_array, int count, bool is_history) return result; } -//TODO OBSOLETE PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile); Datum pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) @@ -1252,99 +1128,6 @@ pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) check_shmem(); - if (SRF_IS_FIRSTCALL()) - { - MemoryContext oldcontext; - TupleDesc tupdesc; - - funcctx = SRF_FIRSTCALL_INIT(); - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - - /* Receive profile from shmq */ - profile = (Profile *) palloc0(sizeof(Profile)); - //profile->items = (ProfileItem *) receive_array(PROFILE_REQUEST, - // sizeof(ProfileItem), &profile->count); - - funcctx->user_fctx = profile; - funcctx->max_calls = profile->count; - - /* Make tuple descriptor */ - tupdesc = CreateTemplateTupleDesc(5); - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 5, "count", - INT8OID, -1, 0); - funcctx->tuple_desc = BlessTupleDesc(tupdesc); - - MemoryContextSwitchTo(oldcontext); - } - - /* stuff done on every call of the function */ - funcctx = SRF_PERCALL_SETUP(); - - profile = (Profile *) funcctx->user_fctx; - - if (funcctx->call_cntr < funcctx->max_calls) - { - /* for each row */ - Datum values[5]; - bool nulls[5]; - HeapTuple tuple; - ProfileItem *item; - const char *event_type, - *event; - - item = &profile->items[funcctx->call_cntr]; - - MemSet(values, 0, sizeof(values)); - MemSet(nulls, 0, sizeof(nulls)); - - /* Make and return next tuple to caller */ - event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); - event = pgstat_get_wait_event(item->dimensions.wait_event_info); - values[0] = Int32GetDatum(item->dimensions.pid); - if (event_type) - values[1] = PointerGetDatum(cstring_to_text(event_type)); - else - nulls[1] = true; - if (event) - values[2] = PointerGetDatum(cstring_to_text(event)); - else - nulls[2] = true; - - if (pgws_profileQueries) - values[3] = UInt64GetDatum(item->dimensions.queryId); - else - values[3] = (Datum) 0; - - values[4] = UInt64GetDatum(item->count); - - tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); - - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); - } - else - { - /* nothing left */ - SRF_RETURN_DONE(funcctx); - } -} - -PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile_extended); -Datum -pg_wait_sampling_get_profile_extended(PG_FUNCTION_ARGS) -{ - Profile *profile; - FuncCallContext *funcctx; - - check_shmem(); - if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; @@ -1467,100 +1250,9 @@ pg_wait_sampling_reset_history(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } -//TODO OBSOLETE PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history); Datum pg_wait_sampling_get_history(PG_FUNCTION_ARGS) -{ - History *history; - FuncCallContext *funcctx; - - check_shmem(); - - if (SRF_IS_FIRSTCALL()) - { - MemoryContext oldcontext; - TupleDesc tupdesc; - - funcctx = SRF_FIRSTCALL_INIT(); - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - - /* Receive history from shmq */ - history = (History *) palloc0(sizeof(History)); - //history->items = (HistoryItem *) receive_array(HISTORY_REQUEST, - // sizeof(HistoryItem), &history->count, &saved_history_dimensions); - - funcctx->user_fctx = history; - funcctx->max_calls = history->count; - - /* Make tuple descriptor */ - tupdesc = CreateTemplateTupleDesc(5); - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sample_ts", - TIMESTAMPTZOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "event", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 5, "queryid", - INT8OID, -1, 0); - funcctx->tuple_desc = BlessTupleDesc(tupdesc); - - MemoryContextSwitchTo(oldcontext); - } - - /* stuff done on every call of the function */ - funcctx = SRF_PERCALL_SETUP(); - - history = (History *) funcctx->user_fctx; - - if (history->index < history->count) - { - //HeapTuple tuple; - //HistoryItem *item; - //Datum values[5]; - //bool nulls[5]; - //const char *event_type, - // *event; - // - //item = &history->items[history->index]; - // - ///* Make and return next tuple to caller */ - //MemSet(values, 0, sizeof(values)); - //MemSet(nulls, 0, sizeof(nulls)); - // - //event_type = pgstat_get_wait_event_type(item->dimensions.wait_event_info); - //event = pgstat_get_wait_event(item->dimensions.wait_event_info); - //values[0] = Int32GetDatum(item->dimensions.pid); - //values[1] = TimestampTzGetDatum(item->ts); - //if (event_type) - // values[2] = PointerGetDatum(cstring_to_text(event_type)); - //else - // nulls[2] = true; - //if (event) - // values[3] = PointerGetDatum(cstring_to_text(event)); - //else - // nulls[3] = true; - // - //values[4] = UInt64GetDatum(item->dimensions.queryId); - //tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); - // - //history->index++; - //SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); - } - else - { - /* nothing left */ - SRF_RETURN_DONE(funcctx); - } - - PG_RETURN_VOID(); -} - -PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history_extended); -Datum -pg_wait_sampling_get_history_extended(PG_FUNCTION_ARGS) { History *history; FuncCallContext *funcctx; From 09fc6620aaaed44e7ea47033e3d87fd5ab26ef58 Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Wed, 25 Jun 2025 09:33:58 +0700 Subject: [PATCH 09/11] Do as pg_stat_statements does with different extension versions --- pg_wait_sampling--1.1--1.2.sql | 6 +- pg_wait_sampling.c | 281 ++++++++++++++++++++++++--------- 2 files changed, 205 insertions(+), 82 deletions(-) diff --git a/pg_wait_sampling--1.1--1.2.sql b/pg_wait_sampling--1.1--1.2.sql index 8653d87..e9499c5 100644 --- a/pg_wait_sampling--1.1--1.2.sql +++ b/pg_wait_sampling--1.1--1.2.sql @@ -42,7 +42,7 @@ CREATE FUNCTION pg_wait_sampling_get_current ( OUT appname text ) RETURNS SETOF record -AS 'MODULE_PATHNAME' +AS 'MODULE_PATHNAME', 'pg_wait_sampling_get_current_1_2' LANGUAGE C VOLATILE CALLED ON NULL INPUT; CREATE VIEW pg_wait_sampling_current AS @@ -68,7 +68,7 @@ CREATE FUNCTION pg_wait_sampling_get_history ( OUT ts timestamptz ) RETURNS SETOF record -AS 'MODULE_PATHNAME' +AS 'MODULE_PATHNAME', 'pg_wait_sampling_get_history_1_2' LANGUAGE C VOLATILE STRICT; CREATE VIEW pg_wait_sampling_history AS @@ -94,7 +94,7 @@ CREATE FUNCTION pg_wait_sampling_get_profile ( OUT count int8 ) RETURNS SETOF record -AS 'MODULE_PATHNAME' +AS 'MODULE_PATHNAME', 'pg_wait_sampling_get_profile_1_2' LANGUAGE C VOLATILE STRICT; CREATE VIEW pg_wait_sampling_profile AS diff --git a/pg_wait_sampling.c b/pg_wait_sampling.c index 2d2b8d0..fe20dba 100644 --- a/pg_wait_sampling.c +++ b/pg_wait_sampling.c @@ -129,6 +129,20 @@ static const struct config_enum_entry pgws_profile_queries_options[] = {NULL, 0, false} }; +/* like in pg_stat_statements */ +typedef enum pgwsVersion +{ + PGWS_V1_1 = 0, + PGWS_V1_2, +} pgwsVersion; + +Datum pg_wait_sampling_get_current_internal(FunctionCallInfo fcinfo, + pgwsVersion api_version); +Datum pg_wait_sampling_get_profile_internal(FunctionCallInfo fcinfo, + pgwsVersion api_version); +Datum pg_wait_sampling_get_history_internal(FunctionCallInfo fcinfo, + pgwsVersion api_version); + int pgws_historySize = 5000; int pgws_historyPeriod = 10; int pgws_profilePeriod = 10; @@ -739,11 +753,16 @@ get_beentry_by_procpid(int pid) return NULL; } +/* like in pg_stat_statements */ +#define PG_WAIT_SAMPLING_COLS_V1_1 5 +#define PG_WAIT_SAMPLING_COLS_V1_2 15 +#define PG_WAIT_SAMPLING_COLS 15 /* maximum of above */ + /* * Common routine to fill "dimensions" part of tupdesc */ static void -fill_tuple_desc (TupleDesc tupdesc) +fill_tuple_desc (TupleDesc tupdesc, pgwsVersion api_version) { TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", INT4OID, -1, 0); @@ -753,30 +772,34 @@ fill_tuple_desc (TupleDesc tupdesc) TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 5, "role_id", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 6, "database_id", - INT8OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 7, "parallel_leader_pid", - INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 8, "is_regular_backend", - BOOLOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_type", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 10, "backend_state", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 11, "proc_start", - TIMESTAMPTZOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_addr", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 13, "client_hostname", - TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 14, "appname", - TEXTOID, -1, 0); + if (api_version >= PGWS_V1_2) + { + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "role_id", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "database_id", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "parallel_leader_pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "is_regular_backend", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "backend_type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "backend_state", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "proc_start", + TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "client_addr", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "client_hostname", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 14, "appname", + TEXTOID, -1, 0); + } } static void -fill_values_and_nulls(Datum *values, bool *nulls, SamplingDimensions dimensions, int dimensions_mask) +fill_values_and_nulls(Datum *values, bool *nulls, SamplingDimensions dimensions, + int dimensions_mask, pgwsVersion api_version) { const char *event_type, *event, @@ -812,53 +835,88 @@ fill_values_and_nulls(Datum *values, bool *nulls, SamplingDimensions dimensions, values[4] = ObjectIdGetDatum(dimensions.role_id); else nulls[4] = true; - if (dimensions_mask & PGWS_DIMENSIONS_DB_ID) - values[5] = ObjectIdGetDatum(dimensions.database_id); - else - nulls[5] = true; - if (dimensions_mask & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) - values[6] = Int32GetDatum(dimensions.parallel_leader_pid); - else - nulls[6] = true; - if (dimensions_mask & PGWS_DIMENSIONS_IS_REGULAR_BE) - values[7] = BoolGetDatum(dimensions.is_regular_backend); - else - nulls[7] = true; - if (backend_type && (dimensions_mask & PGWS_DIMENSIONS_BE_TYPE)) - values[8] = PointerGetDatum(cstring_to_text(backend_type)); - else - nulls[8] = true; - if (!is_null_be_state && (dimensions_mask & PGWS_DIMENSIONS_BE_STATE)) - values[9] = backend_state; - else - nulls[9] = true; - if (dimensions_mask & PGWS_DIMENSIONS_BE_START_TIME) - values[10] = proc_start; - else - nulls[10] = true; - if (!is_null_client_addr && (dimensions_mask & PGWS_DIMENSIONS_CLIENT_ADDR)) - values[11] = client_addr; - else - nulls[11] = true; - if (dimensions_mask & PGWS_DIMENSIONS_CLIENT_HOSTNAME) - values[12] = PointerGetDatum(cstring_to_text(dimensions.client_hostname)); - else - nulls[12] = true; - if (dimensions_mask & PGWS_DIMENSIONS_APPNAME) - values[13] = PointerGetDatum(cstring_to_text(dimensions.appname)); - else - nulls[13] = true; + if (api_version >= PGWS_V1_2) + { + if (dimensions_mask & PGWS_DIMENSIONS_DB_ID) + values[5] = ObjectIdGetDatum(dimensions.database_id); + else + nulls[5] = true; + if (dimensions_mask & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + values[6] = Int32GetDatum(dimensions.parallel_leader_pid); + else + nulls[6] = true; + if (dimensions_mask & PGWS_DIMENSIONS_IS_REGULAR_BE) + values[7] = BoolGetDatum(dimensions.is_regular_backend); + else + nulls[7] = true; + if (backend_type && (dimensions_mask & PGWS_DIMENSIONS_BE_TYPE)) + values[8] = PointerGetDatum(cstring_to_text(backend_type)); + else + nulls[8] = true; + if (!is_null_be_state && (dimensions_mask & PGWS_DIMENSIONS_BE_STATE)) + values[9] = backend_state; + else + nulls[9] = true; + if (dimensions_mask & PGWS_DIMENSIONS_BE_START_TIME) + values[10] = proc_start; + else + nulls[10] = true; + if (!is_null_client_addr && (dimensions_mask & PGWS_DIMENSIONS_CLIENT_ADDR)) + values[11] = client_addr; + else + nulls[11] = true; + if (dimensions_mask & PGWS_DIMENSIONS_CLIENT_HOSTNAME) + values[12] = PointerGetDatum(cstring_to_text(dimensions.client_hostname)); + else + nulls[12] = true; + if (dimensions_mask & PGWS_DIMENSIONS_APPNAME) + values[13] = PointerGetDatum(cstring_to_text(dimensions.appname)); + else + nulls[13] = true; + } } PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current); Datum pg_wait_sampling_get_current(PG_FUNCTION_ARGS) +{ + return pg_wait_sampling_get_current_internal(fcinfo, PGWS_V1_1); +} + +PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current_1_2); +Datum +pg_wait_sampling_get_current_1_2(PG_FUNCTION_ARGS) +{ + return pg_wait_sampling_get_current_internal(fcinfo, PGWS_V1_2); +} + +Datum +pg_wait_sampling_get_current_internal(FunctionCallInfo fcinfo, + pgwsVersion api_version) { FuncCallContext *funcctx; WaitCurrentContext *params; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; check_shmem(); + /* + * Check we have the expected number of output arguments. Safety check + */ + switch (rsinfo->expectedDesc->natts + 1) + { + case PG_WAIT_SAMPLING_COLS_V1_1: + if (api_version != PGWS_V1_1) + elog(ERROR, "incorrect number of output arguments"); + break; + case PG_WAIT_SAMPLING_COLS_V1_2: + if (api_version != PGWS_V1_2) + elog(ERROR, "incorrect number of output arguments"); + break; + default: + elog(ERROR, "incorrect number of output arguments"); + } + /* Initialization, done only on the first call */ if (SRF_IS_FIRSTCALL()) { @@ -873,8 +931,8 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) funcctx->user_fctx = params; /* Setup tuple desc */ - tupdesc = CreateTemplateTupleDesc(14); - fill_tuple_desc (tupdesc); + tupdesc = CreateTemplateTupleDesc(rsinfo->expectedDesc->natts); + fill_tuple_desc (tupdesc, api_version); funcctx->tuple_desc = BlessTupleDesc(tupdesc); LWLockAcquire(ProcArrayLock, LW_SHARED); @@ -884,7 +942,6 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) /* pg_wait_sampling_get_current_extended(pid int4) function */ HistoryItem *item; PGPROC *proc; - //PgBackendStatus *bestatus; not needed? proc = search_proc(PG_GETARG_UINT32(0)); @@ -942,8 +999,8 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) if (funcctx->call_cntr < funcctx->max_calls) { HeapTuple tuple; - Datum values[14]; - bool nulls[14]; + Datum values[PG_WAIT_SAMPLING_COLS - 1]; + bool nulls[PG_WAIT_SAMPLING_COLS - 1]; HistoryItem *item; item = ¶ms->items[funcctx->call_cntr]; @@ -952,7 +1009,7 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - fill_values_and_nulls(values, nulls, item->dimensions, PGWS_DIMENSIONS_ALL); + fill_values_and_nulls(values, nulls, item->dimensions, PGWS_DIMENSIONS_ALL, api_version); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); @@ -1122,12 +1179,44 @@ deserialize_array(void *tmp_array, int count, bool is_history) PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile); Datum pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) +{ + return pg_wait_sampling_get_profile_internal(fcinfo, PGWS_V1_1); +} + +PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile_1_2); +Datum +pg_wait_sampling_get_profile_1_2(PG_FUNCTION_ARGS) +{ + return pg_wait_sampling_get_profile_internal(fcinfo, PGWS_V1_2); +} + +Datum +pg_wait_sampling_get_profile_internal(FunctionCallInfo fcinfo, + pgwsVersion api_version) { Profile *profile; FuncCallContext *funcctx; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; check_shmem(); + /* + * Check we have the expected number of output arguments. Safety check + */ + switch (rsinfo->expectedDesc->natts) + { + case PG_WAIT_SAMPLING_COLS_V1_1: + if (api_version != PGWS_V1_1) + elog(ERROR, "incorrect number of output arguments"); + break; + case PG_WAIT_SAMPLING_COLS_V1_2: + if (api_version != PGWS_V1_2) + elog(ERROR, "incorrect number of output arguments"); + break; + default: + elog(ERROR, "incorrect number of output arguments"); + } + if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; @@ -1148,9 +1237,9 @@ pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) funcctx->max_calls = profile->count; /* Make tuple descriptor */ - tupdesc = CreateTemplateTupleDesc(15); - fill_tuple_desc (tupdesc); - TupleDescInitEntry(tupdesc, (AttrNumber) 15, "count", + tupdesc = CreateTemplateTupleDesc(rsinfo->expectedDesc->natts); + fill_tuple_desc (tupdesc, api_version); + TupleDescInitEntry(tupdesc, (AttrNumber) rsinfo->expectedDesc->natts, "count", INT8OID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); @@ -1165,8 +1254,8 @@ pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) if (funcctx->call_cntr < funcctx->max_calls) { /* for each row */ - Datum values[15]; - bool nulls[15]; + Datum values[PG_WAIT_SAMPLING_COLS]; + bool nulls[PG_WAIT_SAMPLING_COLS]; HeapTuple tuple; ProfileItem *item; @@ -1176,8 +1265,9 @@ pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - fill_values_and_nulls(values, nulls, item->dimensions, pgws_profile_dimensions); - values[14] = UInt64GetDatum(item->count); + fill_values_and_nulls(values, nulls, item->dimensions, + pgws_profile_dimensions, api_version); + values[rsinfo->expectedDesc->natts - 1] = UInt64GetDatum(item->count); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); @@ -1253,17 +1343,49 @@ pg_wait_sampling_reset_history(PG_FUNCTION_ARGS) PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history); Datum pg_wait_sampling_get_history(PG_FUNCTION_ARGS) +{ + return pg_wait_sampling_get_history_internal(fcinfo, PGWS_V1_1); +} + +PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history_1_2); +Datum +pg_wait_sampling_get_history_1_2(PG_FUNCTION_ARGS) +{ + return pg_wait_sampling_get_history_internal(fcinfo, PGWS_V1_2); +} + +Datum +pg_wait_sampling_get_history_internal(FunctionCallInfo fcinfo, + pgwsVersion api_version) { History *history; FuncCallContext *funcctx; - void *tmp_array; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; check_shmem(); + /* + * Check we have the expected number of output arguments. Safety check + */ + switch (rsinfo->expectedDesc->natts) + { + case PG_WAIT_SAMPLING_COLS_V1_1: + if (api_version != PGWS_V1_1) + elog(ERROR, "incorrect number of output arguments"); + break; + case PG_WAIT_SAMPLING_COLS_V1_2: + if (api_version != PGWS_V1_2) + elog(ERROR, "incorrect number of output arguments"); + break; + default: + elog(ERROR, "incorrect number of output arguments"); + } + if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; TupleDesc tupdesc; + void *tmp_array; Size serialized_size; funcctx = SRF_FIRSTCALL_INIT(); @@ -1278,9 +1400,9 @@ pg_wait_sampling_get_history(PG_FUNCTION_ARGS) funcctx->max_calls = history->count; /* Make tuple descriptor */ - tupdesc = CreateTemplateTupleDesc(15); - fill_tuple_desc (tupdesc); - TupleDescInitEntry(tupdesc, (AttrNumber) 15, "sample_ts", //XXX we have moved this to the end to have it more in line with current and profile; debatable; maybe move it to first place? + tupdesc = CreateTemplateTupleDesc(rsinfo->expectedDesc->natts); + fill_tuple_desc (tupdesc, api_version); + TupleDescInitEntry(tupdesc, (AttrNumber) rsinfo->expectedDesc->natts, "sample_ts", //XXX we have moved this to the end to have it more in line with current and profile; debatable; maybe move it to first place? TIMESTAMPTZOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); @@ -1296,8 +1418,8 @@ pg_wait_sampling_get_history(PG_FUNCTION_ARGS) { HeapTuple tuple; HistoryItem *item; - Datum values[15]; - bool nulls[15]; + Datum values[PG_WAIT_SAMPLING_COLS]; + bool nulls[PG_WAIT_SAMPLING_COLS]; item = &history->items[history->index]; @@ -1305,8 +1427,9 @@ pg_wait_sampling_get_history(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - fill_values_and_nulls(values, nulls, item->dimensions, pgws_history_dimensions); - values[14] = TimestampTzGetDatum(item->ts); //XXX same as above + fill_values_and_nulls(values, nulls, item->dimensions, + pgws_history_dimensions, api_version); + values[rsinfo->expectedDesc->natts - 1] = TimestampTzGetDatum(item->ts); //XXX same as above tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); From 42b482814509c6cf63067a5537d04f0d1f6cf10d Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Wed, 25 Jun 2025 16:09:54 +0700 Subject: [PATCH 10/11] Fixes after self-review --- collector.c | 11 ++----- expected/load.out | 76 ++++++++++++++++++++++++++++++-------------- expected/load_1.out | 31 ------------------ expected/queries.out | 45 -------------------------- pg_wait_sampling.c | 31 +++++++++++------- pg_wait_sampling.h | 2 -- sql/queries.sql | 14 -------- 7 files changed, 74 insertions(+), 136 deletions(-) delete mode 100644 expected/load_1.out diff --git a/collector.c b/collector.c index 9e6afad..ec03fee 100644 --- a/collector.c +++ b/collector.c @@ -95,7 +95,6 @@ realloc_history(History *observations, int count) j; int serialized_size; - //saved_history_dimensions = pgws_history_dimensions; // TODO вроде как serialized_size = get_serialized_size(saved_history_dimensions, true); /* Allocate new array for history */ @@ -578,9 +577,6 @@ probe_waits(History *observations, HTAB *profile_hash, LWLockAcquire(ProcArrayLock, LW_SHARED); for (i = 0; i < ProcGlobal->allProcCount; i++) { - //HistoryItem item_history, - // *observation; - //ProfileItem item_profile; PGPROC *proc = &ProcGlobal->allProcs[i]; int pid; uint32 wait_event_info; @@ -611,9 +607,6 @@ probe_waits(History *observations, HTAB *profile_hash, &common_dimensions, saved_profile_dimensions); - //item_history.ts = ts; - //item_history.dimensions = history_dimensions; - /* Write to the history if needed */ if (write_history) { @@ -646,7 +639,7 @@ probe_waits(History *observations, HTAB *profile_hash, &serialized_item, &serialized_key, &serialized_size, (TimestampTz) 0, count, false); - stored_item = (char *) hash_search(profile_hash, serialized_key, + stored_item = (char *) hash_search(profile_hash, serialized_key, HASH_ENTER, &found); if (found) @@ -673,7 +666,7 @@ probe_waits(History *observations, HTAB *profile_hash, * Send waits history to shared memory queue. */ static void -send_history(History *observations, shm_mq_handle *mqh) //TODO TODO TODO +send_history(History *observations, shm_mq_handle *mqh) { int serialized_size = get_serialized_size(saved_history_dimensions, true); Size count, diff --git a/expected/load.out b/expected/load.out index b7de0ac..94e9075 100644 --- a/expected/load.out +++ b/expected/load.out @@ -1,31 +1,61 @@ CREATE EXTENSION pg_wait_sampling; \d pg_wait_sampling_current -View "public.pg_wait_sampling_current" - Column | Type | Modifiers -------------+---------+----------- - pid | integer | - event_type | text | - event | text | - queryid | bigint | + View "public.pg_wait_sampling_current" + Column | Type | Collation | Nullable | Default +---------------------+--------------------------+-----------+----------+--------- + pid | integer | | | + event_type | text | | | + event | text | | | + queryid | bigint | | | + role_id | bigint | | | + database_id | bigint | | | + parallel_leader_pid | integer | | | + is_regular_backend | boolean | | | + backend_type | text | | | + backend_state | text | | | + proc_start | timestamp with time zone | | | + client_addr | text | | | + client_hostname | text | | | + appname | text | | | \d pg_wait_sampling_history - View "public.pg_wait_sampling_history" - Column | Type | Modifiers -------------+--------------------------+----------- - pid | integer | - ts | timestamp with time zone | - event_type | text | - event | text | - queryid | bigint | + View "public.pg_wait_sampling_history" + Column | Type | Collation | Nullable | Default +---------------------+--------------------------+-----------+----------+--------- + pid | integer | | | + event_type | text | | | + event | text | | | + queryid | bigint | | | + role_id | bigint | | | + database_id | bigint | | | + parallel_leader_pid | integer | | | + is_regular_backend | boolean | | | + backend_type | text | | | + backend_state | text | | | + proc_start | timestamp with time zone | | | + client_addr | text | | | + client_hostname | text | | | + appname | text | | | + ts | timestamp with time zone | | | \d pg_wait_sampling_profile -View "public.pg_wait_sampling_profile" - Column | Type | Modifiers -------------+---------+----------- - pid | integer | - event_type | text | - event | text | - queryid | bigint | - count | bigint | + View "public.pg_wait_sampling_profile" + Column | Type | Collation | Nullable | Default +---------------------+--------------------------+-----------+----------+--------- + pid | integer | | | + event_type | text | | | + event | text | | | + queryid | bigint | | | + role_id | bigint | | | + database_id | bigint | | | + parallel_leader_pid | integer | | | + is_regular_backend | boolean | | | + backend_type | text | | | + backend_state | text | | | + proc_start | timestamp with time zone | | | + client_addr | text | | | + client_hostname | text | | | + appname | text | | | + count | bigint | | | DROP EXTENSION pg_wait_sampling; diff --git a/expected/load_1.out b/expected/load_1.out deleted file mode 100644 index 1a1358a..0000000 --- a/expected/load_1.out +++ /dev/null @@ -1,31 +0,0 @@ -CREATE EXTENSION pg_wait_sampling; -\d pg_wait_sampling_current - View "public.pg_wait_sampling_current" - Column | Type | Collation | Nullable | Default -------------+---------+-----------+----------+--------- - pid | integer | | | - event_type | text | | | - event | text | | | - queryid | bigint | | | - -\d pg_wait_sampling_history - View "public.pg_wait_sampling_history" - Column | Type | Collation | Nullable | Default -------------+--------------------------+-----------+----------+--------- - pid | integer | | | - ts | timestamp with time zone | | | - event_type | text | | | - event | text | | | - queryid | bigint | | | - -\d pg_wait_sampling_profile - View "public.pg_wait_sampling_profile" - Column | Type | Collation | Nullable | Default -------------+---------+-----------+----------+--------- - pid | integer | | | - event_type | text | | | - event | text | | | - queryid | bigint | | | - count | bigint | | | - -DROP EXTENSION pg_wait_sampling; diff --git a/expected/queries.out b/expected/queries.out index 6718c14..722df5f 100644 --- a/expected/queries.out +++ b/expected/queries.out @@ -20,27 +20,6 @@ WITH t as (SELECT sum(0) FROM pg_wait_sampling_profile) 0 (1 row) -WITH t as (SELECT sum(0) FROM pg_wait_sampling_current_extended) - SELECT sum(0) FROM generate_series(1, 2), t; - sum ------ - 0 -(1 row) - -WITH t as (SELECT sum(0) FROM pg_wait_sampling_history_extended) - SELECT sum(0) FROM generate_series(1, 2), t; - sum ------ - 0 -(1 row) - -WITH t as (SELECT sum(0) FROM pg_wait_sampling_profile_extended) - SELECT sum(0) FROM generate_series(1, 2), t; - sum ------ - 0 -(1 row) - -- Some dummy checks just to be sure that all our functions work and return something. SELECT count(*) = 1 as test FROM pg_wait_sampling_get_current(pg_backend_pid()); test @@ -66,28 +45,4 @@ SELECT pg_wait_sampling_reset_profile(); (1 row) -SELECT count(*) = 1 as test FROM pg_wait_sampling_get_current_extended(pg_backend_pid()); - test ------- - t -(1 row) - -SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_profile_extended(); - test ------- - t -(1 row) - -SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_history_extended(); - test ------- - t -(1 row) - -SELECT pg_wait_sampling_reset_profile(); - pg_wait_sampling_reset_profile --------------------------------- - -(1 row) - DROP EXTENSION pg_wait_sampling; diff --git a/pg_wait_sampling.c b/pg_wait_sampling.c index fe20dba..6a82bba 100644 --- a/pg_wait_sampling.c +++ b/pg_wait_sampling.c @@ -334,11 +334,9 @@ pgws_general_dimensions_check_hook (char **newvalue, void **extra, GucSource sou int extrachecks = 0; int *myextra; - /* Check special cases when we turn all or none dimensions */ + /* Check special case when we turn all dimensions */ if (pg_strcasecmp(*newvalue, "all") == 0) extrachecks = PGWS_DIMENSIONS_ALL; - else if (pg_strcasecmp(*newvalue, "none") == 0) - extrachecks = PGWS_DIMENSIONS_NONE; else { /* Need a modifiable copy of string */ @@ -388,7 +386,7 @@ pgws_general_dimensions_check_hook (char **newvalue, void **extra, GucSource sou extrachecks |= PGWS_DIMENSIONS_CLIENT_HOSTNAME; else if (pg_strcasecmp(tok, "appname") == 0) extrachecks |= PGWS_DIMENSIONS_APPNAME; - else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0) + else if (pg_strcasecmp(tok, "all") == 0) { GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok); pfree(rawstring); @@ -747,8 +745,17 @@ get_beentry_by_procpid(int pid) /* Here beid is just index in localBackendStatusTable */ local_beentry = pgstat_fetch_stat_local_beentry(cur_be_idx); #endif +#if defined(PGPRO_EE) || defined(PGPRO_STD) && PG_VERSION_NUM >= 160000 if (local_beentry->backendStatus->st_procpid == pid) return local_beentry->backendStatus; +#else + if (local_beentry->backendStatus.st_procpid == pid) + { + PgBackendStatus *result = palloc0(sizeof(PgBackendStatus)); + *result = local_beentry->backendStatus; + return result; + } +#endif } return NULL; } @@ -831,12 +838,12 @@ fill_values_and_nulls(Datum *values, bool *nulls, SamplingDimensions dimensions, values[3] = UInt64GetDatum(dimensions.queryId); else values[3] = (Datum) 0; - if (dimensions_mask & PGWS_DIMENSIONS_ROLE_ID) - values[4] = ObjectIdGetDatum(dimensions.role_id); - else - nulls[4] = true; if (api_version >= PGWS_V1_2) { + if (dimensions_mask & PGWS_DIMENSIONS_ROLE_ID) + values[4] = ObjectIdGetDatum(dimensions.role_id); + else + nulls[4] = true; if (dimensions_mask & PGWS_DIMENSIONS_DB_ID) values[5] = ObjectIdGetDatum(dimensions.database_id); else @@ -887,7 +894,7 @@ PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current_1_2); Datum pg_wait_sampling_get_current_1_2(PG_FUNCTION_ARGS) { - return pg_wait_sampling_get_current_internal(fcinfo, PGWS_V1_2); + return pg_wait_sampling_get_current_internal(fcinfo, PGWS_V1_2); } Datum @@ -1187,7 +1194,7 @@ PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile_1_2); Datum pg_wait_sampling_get_profile_1_2(PG_FUNCTION_ARGS) { - return pg_wait_sampling_get_profile_internal(fcinfo, PGWS_V1_2); + return pg_wait_sampling_get_profile_internal(fcinfo, PGWS_V1_2); } Datum @@ -1328,7 +1335,7 @@ pg_wait_sampling_reset_history(PG_FUNCTION_ARGS) LockAcquire(&collectorTag, ExclusiveLock, false, false); pgws_collector_hdr->request = HISTORY_RESET; LockRelease(&collectorTag, ExclusiveLock, false); - + if (!pgws_collector_hdr->latch) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("pg_wait_sampling collector wasn't started"))); @@ -1351,7 +1358,7 @@ PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history_1_2); Datum pg_wait_sampling_get_history_1_2(PG_FUNCTION_ARGS) { - return pg_wait_sampling_get_history_internal(fcinfo, PGWS_V1_2); + return pg_wait_sampling_get_history_internal(fcinfo, PGWS_V1_2); } Datum diff --git a/pg_wait_sampling.h b/pg_wait_sampling.h index 3b84b60..5f457f4 100644 --- a/pg_wait_sampling.h +++ b/pg_wait_sampling.h @@ -28,8 +28,6 @@ #define PGWS_COLLECTOR_LOCK 1 /* Values for sampling dimensions */ -#define PGWS_DIMENSIONS_NONE 0 - #define PGWS_DIMENSIONS_PID (1 << 0) #define PGWS_DIMENSIONS_WAIT_EVENT_TYPE (1 << 1) #define PGWS_DIMENSIONS_WAIT_EVENT (1 << 2) diff --git a/sql/queries.sql b/sql/queries.sql index 6658c74..de44c6d 100644 --- a/sql/queries.sql +++ b/sql/queries.sql @@ -9,24 +9,10 @@ WITH t as (SELECT sum(0) FROM pg_wait_sampling_history) WITH t as (SELECT sum(0) FROM pg_wait_sampling_profile) SELECT sum(0) FROM generate_series(1, 2), t; -WITH t as (SELECT sum(0) FROM pg_wait_sampling_current_extended) - SELECT sum(0) FROM generate_series(1, 2), t; - -WITH t as (SELECT sum(0) FROM pg_wait_sampling_history_extended) - SELECT sum(0) FROM generate_series(1, 2), t; - -WITH t as (SELECT sum(0) FROM pg_wait_sampling_profile_extended) - SELECT sum(0) FROM generate_series(1, 2), t; - -- Some dummy checks just to be sure that all our functions work and return something. SELECT count(*) = 1 as test FROM pg_wait_sampling_get_current(pg_backend_pid()); SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_profile(); SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_history(); SELECT pg_wait_sampling_reset_profile(); -SELECT count(*) = 1 as test FROM pg_wait_sampling_get_current_extended(pg_backend_pid()); -SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_profile_extended(); -SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_history_extended(); -SELECT pg_wait_sampling_reset_profile(); - DROP EXTENSION pg_wait_sampling; From 01c45a43e6bca7f664bfbab41dcfca290b1cfcfa Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Thu, 26 Jun 2025 11:36:32 +0700 Subject: [PATCH 11/11] Fix and add info about sampling dimensions to README --- README.md | 81 +++++++++++++++++-------------------------------------- 1 file changed, 25 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index f5f68cd..50e742e 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ When `pg_wait_sampling` is enabled, it collects two kinds of statistics. a client who periodically reads this history and dumps it somewhere, user can have continuous history of wait events. * Waits profile. It's implemented as in-memory hash table where samples - are accumulated per each wait event and can be divided by process, + are accumulated per each wait event and can be divided by process, wait event, query and other dimensions. This hash table can be reset by user request. Assuming there is a client who periodically dumps profile and resets it, user can have statistics of wait events over time. @@ -99,20 +99,6 @@ Usage `pg_wait_sampling_current` view – information about current wait events for all processes including background workers. -| Column name | Column type | Description | -| ----------- | ----------- | ----------------------- | -| pid | int4 | Id of process | -| event_type | text | Name of wait event type | -| event | text | Name of wait event | -| queryid | int8 | Id of query | - -`pg_wait_sampling_get_current(pid int4)` returns the same table for single given -process. - -`pg_wait_sampling_current_extended` view – information about current wait events for -all processes including background workers. Structure of this view can be changed -between verions. - | Column name | Column type | Description | | ------------------- | ----------- | --------------------------- | | pid | int4 | Id of process | @@ -129,28 +115,15 @@ between verions. | client_hostname | text | Client hostname | | appname | text | Application name | -`pg_wait_sampling_get_current_extended(pid int4)` returns the same table for single given +`pg_wait_sampling_get_current(pid int4)` returns the same table for single given process. `pg_wait_sampling_history` view – history of wait events obtained by sampling into in-memory ring buffer. -| Column name | Column type | Description | -| ----------- | ----------- | ----------------------- | -| pid | int4 | Id of process | -| ts | timestamptz | Sample timestamp | -| event_type | text | Name of wait event type | -| event | text | Name of wait event | -| queryid | int8 | Id of query | - -`pg_wait_sampling_history_extended` view – history of wait events obtained by -sampling into in-memory ring buffer. Structure of this view can be changed -between verions - | Column name | Column type | Description | | ------------------- | ----------- | --------------------------- | | pid | int4 | Id of process | -| ts | timestamptz | Sample timestamp | | event_type | text | Name of wait event type | | event | text | Name of wait event | | queryid | int8 | Id of query | @@ -163,22 +136,13 @@ between verions | client_addr | text | Client address | | client_hostname | text | Client hostname | | appname | text | Application name | +| ts | timestamptz | Sample timestamp | + +`pg_wait_sampling_reset_history()` function resets the history. `pg_wait_sampling_profile` view – profile of wait events obtained by sampling into in-memory hash table. -| Column name | Column type | Description | -| ----------- | ----------- | ----------------------- | -| pid | int4 | Id of process | -| event_type | text | Name of wait event type | -| event | text | Name of wait event | -| queryid | int8 | Id of query | -| count | int8 | Count of samples | - -`pg_wait_sampling_profile_extended` view – history of wait events obtained by -sampling into in-memory ring buffer. Structure of this view can be changed -between verions - | Column name | Column type | Description | | ------------------- | ----------- | --------------------------- | | pid | int4 | Id of process | @@ -201,16 +165,16 @@ between verions The work of wait event statistics collector worker is controlled by following GUCs. -| Parameter name | Data type | Description | Default value | -|-------------------------------------| --------- |---------------------------------------------|--------------:| -| pg_wait_sampling.history_size | int4 | Size of history in-memory ring buffer | 5000 | -| pg_wait_sampling.history_period | int4 | Period for history sampling in milliseconds | 10 | -| pg_wait_sampling.profile_period | int4 | Period for profile sampling in milliseconds | 10 | -| pg_wait_sampling.profile_pid | bool | Whether profile should be per pid | true | -| pg_wait_sampling.profile_queries | enum | Whether profile should be per query | top | -| pg_wait_sampling.sample_cpu | bool | Whether on CPU backends should be sampled | true | -| pg_wait_sampling.history_dimensions | text | Additional columns in extended history view | 'none' | -| pg_wait_sampling.profile_dimensions | text | Additional columns in extended profile view | 'none' | +| Parameter name | Data type | Description | Default value | +|-------------------------------------| --------- |---------------------------------------------|----------------------------------------------| +| pg_wait_sampling.history_size | int4 | Size of history in-memory ring buffer | 5000 | +| pg_wait_sampling.history_period | int4 | Period for history sampling in milliseconds | 10 | +| pg_wait_sampling.profile_period | int4 | Period for profile sampling in milliseconds | 10 | +| pg_wait_sampling.profile_pid | bool | Whether profile should be per pid | true | +| pg_wait_sampling.profile_queries | enum | Whether profile should be per query | top | +| pg_wait_sampling.sample_cpu | bool | Whether on CPU backends should be sampled | true | +| pg_wait_sampling.history_dimensions | text | Additional columns in extended history view | 'pid, wait_event_type, wait_event, query_id' | +| pg_wait_sampling.profile_dimensions | text | Additional columns in extended profile view | 'pid, wait_event_type, wait_event, query_id' | If `pg_wait_sampling.profile_pid` is set to false, sampling profile wouldn't be collected in per-process manner. In this case the value of pid could would @@ -226,10 +190,11 @@ will be NULL. `pg_wait_sampling.history_dimenstions` and `pg_wait_sampling.profile_dimensions` determine what additional columns will be sampled in `history/profile_extended` -views. Possible values are `none`, `all`, `role_id`, `database_id`, -`parallel_leader_pid`, `backend_type`, `backend_state`, `backend_start_time`, -`client_addr`, `client_hostname`, `appname` and any combination of column names. -`none` and `all` cannot be used together with any other values and must be used alone. +views. Possible values are `all`, `pid`, `wait_event_type`, `wait_event`, +`query_id`, `role_id`, `database_id`, `parallel_leader_pid`, `backend_type`, +`backend_state`, `backend_start_time`, `client_addr`, `client_hostname`, +`appname` and any combination of column names. +`all` cannot be used together with any other values and must be used alone. > [!WARNING] > Turning on any of the following columns: `backend_type`, `backend_state`, @@ -238,9 +203,13 @@ views. Possible values are `none`, `all`, `role_id`, `database_id`, > BackendStatusTable. This is especially noticeable with PostgreSQL 13-16 Values of these GUC variables can be changed only in config file or with ALTER SYSTEM. -Then you need to reload server's configuration (such as with pg_reload_conf function) +Then you need to reload server's configuration (such as with `pg_reload_conf` function) for changes to take effect. +> [!WARNING] +> When using `pg_reload_conf` you also need to invoke `pg_wait_sampling_reset_history()` +> and `pg_wait_sampling_reset_profile()` for correct application of new dimensions + See [PostgreSQL documentation](http://www.postgresql.org/docs/devel/static/monitoring-stats.html#WAIT-EVENT-TABLE) for list of possible wait events.