From ec9549d1342a68e608a05f7c9af468e41f15fc4b Mon Sep 17 00:00:00 2001 From: Oleg Tselebrovskiy Date: Wed, 1 Oct 2025 12:53:14 +0700 Subject: [PATCH] Add role_id, database_id, leader_pid and is_regular_backend columns Also add a GUC to handle all "sampling dimensions" --- Makefile | 2 +- README.md | 91 +++--- collector.c | 128 ++++++--- expected/load.out | 58 ++-- expected/load_1.out | 31 --- meson.build | 1 + pg_wait_sampling--1.1--1.2.sql | 85 ++++++ pg_wait_sampling.c | 489 +++++++++++++++++++++++++-------- pg_wait_sampling.control | 2 +- pg_wait_sampling.h | 48 +++- 10 files changed, 677 insertions(+), 258 deletions(-) delete mode 100644 expected/load_1.out 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/README.md b/README.md index bbdbd20..24e8464 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,9 @@ When `pg_wait_sampling` is enabled, it collects two kinds of statistics. 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 + * Waits profile. It's implemented as in-memory hash table where samples + are accumulated and can be grouped by process, wait event, query, user and/or + database (and then joined by queryid 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. @@ -98,14 +98,18 @@ 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. - -| 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 | +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 | +| role_id | int4 | Id of role | +| database_id | int4 | Id of database | +| leader_pid | int4 | Id of parallel query leader | +| is_regular_backend | bool | Is backend or worker | `pg_wait_sampling_get_current(pid int4)` returns the same table for single given process. @@ -113,38 +117,48 @@ 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 | +| 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 | +| leader_pid | int4 | Id of parallel query leader | +| is_regular_backend | bool | Is backend or worker | +| ts | timestamptz | Sample timestamp | `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 | text | Count of samples | +| 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 | +| leader_pid | int4 | Id of parallel query leader | +| is_regular_backend | bool | Is backend or worker | +| count | text | 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 | Columns that are sampled for history | 'pid, event, queryid' | +| pg_wait_sampling.profile_dimensions | text | Columns that are sampled for profile | 'pid, event, queryid' | 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,10 +172,21 @@ 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 columns will be sampled in `history/profile` views. +Allowed values are `all`, `pid`, `event`, `query_id`, `role_id`, +`database_id`, `leader_pid` and any combination of column names. +`event` turns on and off both event and event_type columns. +`all` cannot be used together with any other values and must be used alone. + 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. +> [!WARNING] +> Using `pg_reload_conf` will reset `pg_wait_sampling_history` and +> `pg_wait_sampling_profile` views if new dimensions differ from old ones. + See [PostgreSQL documentation](http://www.postgresql.org/docs/devel/static/monitoring-stats.html#WAIT-EVENT-TABLE) for list of possible wait events. diff --git a/collector.c b/collector.c index 721299f..f869629 100644 --- a/collector.c +++ b/collector.c @@ -32,6 +32,9 @@ static volatile sig_atomic_t shutdown_requested = false; +int saved_profile_dimensions; +int saved_history_dimensions; + static void handle_sigterm(SIGNAL_ARGS); /* @@ -61,7 +64,8 @@ pgws_register_wait_collector(void) static void alloc_history(History *observations, int count) { - observations->items = (HistoryItem *) palloc0(sizeof(HistoryItem) * count); + saved_history_dimensions = pgws_history_dimensions; + observations->samples = (Sample *) palloc0(sizeof(Sample) * count); observations->index = 0; observations->count = count; observations->wraparound = false; @@ -73,13 +77,13 @@ alloc_history(History *observations, int count) static void realloc_history(History *observations, int count) { - HistoryItem *newitems; + Sample *newitems; int copyCount, i, j; /* Allocate new array for history */ - newitems = (HistoryItem *) palloc0(sizeof(HistoryItem) * count); + newitems = (Sample *) palloc0(sizeof(Sample) * count); /* Copy entries from old array to the new */ if (observations->wraparound) @@ -98,14 +102,14 @@ realloc_history(History *observations, int count) { if (j >= observations->count) j = 0; - memcpy(&newitems[i], &observations->items[j], sizeof(HistoryItem)); + memcpy(&newitems[i], &observations->samples[j], sizeof(Sample)); i++; j++; } /* Switch to new history array */ - pfree(observations->items); - observations->items = newitems; + pfree(observations->samples); + observations->samples = newitems; observations->index = copyCount; observations->count = count; observations->wraparound = false; @@ -125,10 +129,10 @@ handle_sigterm(SIGNAL_ARGS) /* * Get next item of history with rotation. */ -static HistoryItem * +static Sample * get_next_observation(History *observations) { - HistoryItem *result; + Sample *result; /* Check for wraparound */ if (observations->index >= observations->count) @@ -136,11 +140,44 @@ get_next_observation(History *observations) observations->index = 0; observations->wraparound = true; } - result = &observations->items[observations->index]; + result = &observations->samples[observations->index]; observations->index++; return result; } +void +fill_sample(Sample *sample, 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; +#if PG_VERSION_NUM >= 180000 + bool is_regular_backend = proc->isRegularBackend; +#else + bool is_regular_backend = !proc->isBackgroundWorker; +#endif + + if (dimensions_mask & PGWS_DIMENSIONS_PID) + sample->pid = pid; + if (dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT) + sample->wait_event_info = wait_event_info; + if (pgws_profileQueries || (dimensions_mask & PGWD_DIMENSIONS_QUERY_ID)) + sample->queryId = queryId; + /* Copy everything else we need from PGPROC */ + if (dimensions_mask & PGWS_DIMENSIONS_ROLE_ID) + sample->role_id = role_id; + if (dimensions_mask & PGWS_DIMENSIONS_DB_ID) + sample->database_id = database_id; + if (dimensions_mask & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + sample->parallel_leader_pid = (lockGroupLeader && + lockGroupLeader->pid != pid ? + lockGroupLeader->pid : + 0); + if (dimensions_mask & PGWS_DIMENSIONS_IS_REGULAR_BE) + sample->is_regular_backend = is_regular_backend; +} + /* * Read current waits from backends and write them to history array * and/or profile hash. @@ -162,37 +199,38 @@ probe_waits(History *observations, HTAB *profile_hash, LWLockAcquire(ProcArrayLock, LW_SHARED); for (i = 0; i < ProcGlobal->allProcCount; i++) { - HistoryItem item, - *observation; - PGPROC *proc = &ProcGlobal->allProcs[i]; + /* We do not copy PGPROC since it is very big */ + PGPROC *proc = &ProcGlobal->allProcs[i]; + int pid; + uint32 wait_event_info; - if (!pgws_should_sample_proc(proc, &item.pid, &item.wait_event_info)) + if (!pgws_should_sample_proc(proc, &pid, &wait_event_info)) continue; - if (pgws_profileQueries) - item.queryId = pgws_proc_queryids[i]; - else - item.queryId = 0; - - item.ts = ts; - /* Write to the history if needed */ if (write_history) { - observation = get_next_observation(observations); - *observation = item; + Sample *observation = get_next_observation(observations); + memset(observation, 0, sizeof(Sample)); + fill_sample(observation, proc, pid, wait_event_info, + pgws_proc_queryids[i], saved_history_dimensions); + observation->ts = ts; } /* Write to the profile if needed */ if (write_profile) { - ProfileItem *profileItem; - bool found; + Sample *profileItem; + Sample key; + bool found; + memset(&key, 0, sizeof(Sample)); + fill_sample(&key, proc, pid, wait_event_info, + pgws_proc_queryids[i], saved_profile_dimensions); if (!profile_pid) - item.pid = 0; + key.pid = 0; - profileItem = (ProfileItem *) hash_search(profile_hash, &item, HASH_ENTER, &found); + profileItem = (Sample *) hash_search(profile_hash, &key, HASH_ENTER, &found); if (found) profileItem->count++; else @@ -229,8 +267,8 @@ send_history(History *observations, shm_mq_handle *mqh) for (i = 0; i < count; i++) { mq_result = shm_mq_send_compat(mqh, - sizeof(HistoryItem), - &observations->items[i], + sizeof(Sample), + &observations->samples[i], false, true); if (mq_result == SHM_MQ_DETACHED) @@ -249,10 +287,10 @@ send_history(History *observations, shm_mq_handle *mqh) static void send_profile(HTAB *profile_hash, shm_mq_handle *mqh) { - HASH_SEQ_STATUS scan_status; - ProfileItem *item; - Size count = hash_get_num_entries(profile_hash); - shm_mq_result mq_result; + HASH_SEQ_STATUS scan_status; + Sample *sample; + Size count = hash_get_num_entries(profile_hash); + shm_mq_result mq_result; /* Send array size first since receive_array expects this */ mq_result = shm_mq_send_compat(mqh, sizeof(count), &count, false, true); @@ -264,9 +302,9 @@ send_profile(HTAB *profile_hash, shm_mq_handle *mqh) return; } hash_seq_init(&scan_status, profile_hash); - while ((item = (ProfileItem *) hash_seq_search(&scan_status)) != NULL) + while ((sample = (Sample *) hash_seq_search(&scan_status)) != NULL) { - mq_result = shm_mq_send_compat(mqh, sizeof(ProfileItem), item, false, + mq_result = shm_mq_send_compat(mqh, sizeof(Sample), sample, false, true); if (mq_result == SHM_MQ_DETACHED) { @@ -287,12 +325,10 @@ make_profile_hash() { HASHCTL hash_ctl; - if (pgws_profileQueries) - hash_ctl.keysize = offsetof(ProfileItem, count); - else - hash_ctl.keysize = offsetof(ProfileItem, queryId); - - hash_ctl.entrysize = sizeof(ProfileItem); + saved_profile_dimensions = pgws_profile_dimensions; + /* Fields that are not in dimensions mask are zero and are included in key */ + hash_ctl.keysize = offsetof(Sample, count); + hash_ctl.entrysize = sizeof(Sample); return hash_create("Waits profile hash", 1024, &hash_ctl, HASH_ELEM | HASH_BLOBS); } @@ -377,6 +413,18 @@ pgws_collector_main(Datum main_arg) { ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); + + /* Reset profile and history if needed */ + if (pgws_history_dimensions != saved_history_dimensions) + { + pfree(observations.samples); + alloc_history(&observations, pgws_historySize); + } + if (pgws_profile_dimensions != saved_profile_dimensions) + { + hash_destroy(profile_hash); + profile_hash = make_profile_hash(); + } } /* Calculate time to next sample for history or profile */ diff --git a/expected/load.out b/expected/load.out index b7de0ac..db6997b 100644 --- a/expected/load.out +++ b/expected/load.out @@ -1,31 +1,43 @@ 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 | | | + leader_pid | integer | | | + is_regular_backend | boolean | | | \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 | | | + leader_pid | integer | | | + is_regular_backend | boolean | | | + 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 | | | + leader_pid | integer | | | + is_regular_backend | boolean | | | + 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/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..52392a4 --- /dev/null +++ b/pg_wait_sampling--1.1--1.2.sql @@ -0,0 +1,85 @@ +/* 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 + +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 ( + pid int4, + OUT pid int4, + OUT event_type text, + OUT event text, + OUT queryid int8, + OUT role_id int8, + OUT database_id int8, + OUT leader_pid int4, + OUT is_regular_backend bool +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_wait_sampling_get_current_1_2' +LANGUAGE C VOLATILE CALLED ON NULL INPUT; + +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 ( + OUT pid int4, + OUT event_type text, + OUT event text, + OUT queryid int8, + OUT role_id int8, + OUT database_id int8, + OUT leader_pid int4, + OUT is_regular_backend bool, + OUT ts timestamptz +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_wait_sampling_get_history_1_2' +LANGUAGE C VOLATILE STRICT; + +CREATE VIEW pg_wait_sampling_history AS + SELECT * FROM pg_wait_sampling_get_history(); + +GRANT SELECT ON pg_wait_sampling_history TO PUBLIC; + +CREATE FUNCTION pg_wait_sampling_get_profile ( + OUT pid int4, + OUT event_type text, + OUT event text, + OUT queryid int8, + OUT role_id int8, + OUT database_id int8, + OUT leader_pid int4, + OUT is_regular_backend bool, + OUT count int8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_wait_sampling_get_profile_1_2' +LANGUAGE C VOLATILE STRICT; + +CREATE VIEW pg_wait_sampling_profile AS + SELECT * FROM pg_wait_sampling_get_profile(); + +GRANT SELECT ON pg_wait_sampling_profile TO PUBLIC; \ No newline at end of file diff --git a/pg_wait_sampling.c b/pg_wait_sampling.c index e165a6a..296e9a8 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" @@ -127,12 +129,30 @@ 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; 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 +321,113 @@ 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 dimensions_mask = 0; + int *myextra; + + /* Check special case when we turn all dimensions */ + if (pg_strcasecmp(*newvalue, "all") == 0) + dimensions_mask = PGWS_DIMENSIONS_ALL; + else + { + /* Empty strings are not allowed */ + if (strlen(*newvalue) == 0) + { + GUC_check_errdetail("Empty string is not allowed"); + return false; + } + + /* 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, "pid") == 0) + dimensions_mask |= PGWS_DIMENSIONS_PID; + else if (pg_strcasecmp(tok, "event") == 0) + { + dimensions_mask |= PGWS_DIMENSIONS_WAIT_EVENT; + } + else if (pg_strcasecmp(tok, "queryid") == 0) + dimensions_mask |= PGWD_DIMENSIONS_QUERY_ID; + else if (pg_strcasecmp(tok, "role_id") == 0) + dimensions_mask |= PGWS_DIMENSIONS_ROLE_ID; + else if (pg_strcasecmp(tok, "database_id") == 0) + dimensions_mask |= PGWS_DIMENSIONS_DB_ID; + else if (pg_strcasecmp(tok, "leader_pid") == 0) + dimensions_mask |= PGWS_DIMENSIONS_PARALLEL_LEADER_PID; + else if (pg_strcasecmp(tok, "is_regular_backend") == 0) + dimensions_mask |= PGWS_DIMENSIONS_IS_REGULAR_BE; + else if (pg_strcasecmp(tok, "all") == 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 = dimensions_mask; + *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 +548,28 @@ _PG_init(void) NULL, NULL); + DefineCustomStringVariable("pg_wait_sampling.history_dimensions", + "Sets sampling dimensions for history", + NULL, + &pgws_history_dimensions_string, + "pid, event, queryid", + 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, + "pid, event, queryid", + 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 @@ -483,19 +632,129 @@ pgws_should_sample_proc(PGPROC *proc, int *pid_p, uint32 *wait_event_info_p) typedef struct { - HistoryItem *items; - TimestampTz ts; + Sample *samples; + TimestampTz ts; } WaitCurrentContext; +/* Like in pg_stat_statements */ +#define PG_WAIT_SAMPLING_COLS_V1_1 5 +#define PG_WAIT_SAMPLING_COLS_V1_2 9 +#define PG_WAIT_SAMPLING_COLS 9 /* maximum of above */ + +/* + * Common routine to fill "dimensions" part of tupdesc + */ +static void +fill_tuple_desc (TupleDesc tupdesc, pgwsVersion api_version) +{ + 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); + 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, "leader_pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "is_regular_backend", + BOOLOID, -1, 0); + } +} + +static void +fill_values_and_nulls(Datum *values, bool *nulls, Sample sample, + int dimensions_mask, pgwsVersion api_version) +{ + if (dimensions_mask & PGWS_DIMENSIONS_PID) + values[0] = Int32GetDatum(sample.pid); + else + nulls[0] = true; + if (sample.wait_event_info != 0 && (dimensions_mask & PGWS_DIMENSIONS_WAIT_EVENT)) + { + values[1] = PointerGetDatum(cstring_to_text(pgstat_get_wait_event_type(sample.wait_event_info))); + values[2] = PointerGetDatum(cstring_to_text(pgstat_get_wait_event(sample.wait_event_info))); + } + else + { + nulls[1] = true; + nulls[2] = true; + } + if (pgws_profileQueries || (dimensions_mask & PGWD_DIMENSIONS_QUERY_ID)) + values[3] = UInt64GetDatum(sample.queryId); + else + nulls[3] = true; + if (api_version >= PGWS_V1_2) + { + if (dimensions_mask & PGWS_DIMENSIONS_ROLE_ID) + values[4] = ObjectIdGetDatum(sample.role_id); + else + nulls[4] = true; + if (dimensions_mask & PGWS_DIMENSIONS_DB_ID) + values[5] = ObjectIdGetDatum(sample.database_id); + else + nulls[5] = true; + if (dimensions_mask & PGWS_DIMENSIONS_PARALLEL_LEADER_PID) + values[6] = Int32GetDatum(sample.parallel_leader_pid); + else + nulls[6] = true; + if (dimensions_mask & PGWS_DIMENSIONS_IS_REGULAR_BE) + values[7] = BoolGetDatum(sample.is_regular_backend); + else + nulls[7] = 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 + * + * +1 is because pg_wait_sampling_current doesn't have count/ts column + */ + switch(api_version) + { + case PGWS_V1_1: + if (rsinfo->expectedDesc->natts + 1 != PG_WAIT_SAMPLING_COLS_V1_1) + elog(ERROR, "incorrect number of output arguments"); + break; + case PGWS_V1_2: + if (rsinfo->expectedDesc->natts + 1 != PG_WAIT_SAMPLING_COLS_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()) { MemoryContext oldcontext; @@ -508,16 +767,9 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) 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); - + /* Setup tuple desc */ + tupdesc = CreateTemplateTupleDesc(rsinfo->expectedDesc->natts); + fill_tuple_desc (tupdesc, api_version); funcctx->tuple_desc = BlessTupleDesc(tupdesc); LWLockAcquire(ProcArrayLock, LW_SHARED); @@ -525,15 +777,15 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) if (!PG_ARGISNULL(0)) { /* pg_wait_sampling_get_current(pid int4) function */ - HistoryItem *item; - PGPROC *proc; + Sample *sample; + PGPROC *proc; 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]; + params->samples = (Sample *) palloc0(sizeof(Sample)); + sample = ¶ms->samples[0]; + fill_sample(sample, proc, proc->pid, proc->wait_event_info, //TODO should we save pid and wait_event_info like in probe_waits? + pgws_proc_queryids[proc - ProcGlobal->allProcs], + PGWS_DIMENSIONS_ALL); funcctx->max_calls = 1; } else @@ -543,19 +795,19 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) i, j = 0; - params->items = (HistoryItem *) palloc0(sizeof(HistoryItem) * procCount); + params->samples = (Sample *) palloc0(sizeof(Sample) * procCount); for (i = 0; i < procCount; i++) { PGPROC *proc = &ProcGlobal->allProcs[i]; if (!pgws_should_sample_proc(proc, - ¶ms->items[j].pid, - ¶ms->items[j].wait_event_info)) + ¶ms->samples[j].pid, + ¶ms->samples[j].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]; + fill_sample(¶ms->samples[j], proc, proc->pid, proc->wait_event_info, //TODO should we save pid and wait_event_info like in probe_waits? + pgws_proc_queryids[proc - ProcGlobal->allProcs], + PGWS_DIMENSIONS_ALL); j++; } funcctx->max_calls = j; @@ -573,31 +825,18 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) if (funcctx->call_cntr < funcctx->max_calls) { HeapTuple tuple; - Datum values[4]; - bool nulls[4]; - const char *event_type, - *event; - HistoryItem *item; + Datum values[PG_WAIT_SAMPLING_COLS - 1]; + bool nulls[PG_WAIT_SAMPLING_COLS - 1]; + Sample *sample; - item = ¶ms->items[funcctx->call_cntr]; + sample = ¶ms->samples[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); - 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; + fill_values_and_nulls(values, nulls, *sample, PGWS_DIMENSIONS_ALL, api_version); - values[3] = UInt64GetDatum(item->queryId); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); @@ -612,7 +851,7 @@ pg_wait_sampling_get_current(PG_FUNCTION_ARGS) typedef struct { Size count; - ProfileItem *items; + Sample *samples; } Profile; void @@ -717,11 +956,43 @@ PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile); Datum pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) { - Profile *profile; - FuncCallContext *funcctx; + 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(api_version) + { + case PGWS_V1_1: + if (rsinfo->expectedDesc->natts != PG_WAIT_SAMPLING_COLS_V1_1) + elog(ERROR, "incorrect number of output arguments"); + break; + case PGWS_V1_2: + if (rsinfo->expectedDesc->natts != PG_WAIT_SAMPLING_COLS_V1_2) + elog(ERROR, "incorrect number of output arguments"); + break; + default: + elog(ERROR, "incorrect number of output arguments"); + } + if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; @@ -732,23 +1003,16 @@ pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) /* Receive profile from shmq */ profile = (Profile *) palloc0(sizeof(Profile)); - profile->items = (ProfileItem *) receive_array(PROFILE_REQUEST, - sizeof(ProfileItem), &profile->count); + profile->samples = (Sample *) receive_array(PROFILE_REQUEST, + sizeof(Sample), &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", + 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); @@ -763,37 +1027,20 @@ pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) if (funcctx->call_cntr < funcctx->max_calls) { /* for each row */ - Datum values[5]; - bool nulls[5]; + Datum values[PG_WAIT_SAMPLING_COLS]; + bool nulls[PG_WAIT_SAMPLING_COLS]; HeapTuple tuple; - ProfileItem *item; - const char *event_type, - *event; + Sample *sample; - item = &profile->items[funcctx->call_cntr]; + sample = &profile->samples[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->wait_event_info); - event = pgstat_get_wait_event(item->wait_event_info); - 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; - - values[4] = UInt64GetDatum(item->count); + fill_values_and_nulls(values, nulls, *sample, + pgws_profile_dimensions, api_version); + values[rsinfo->expectedDesc->natts - 1] = UInt64GetDatum(sample->count); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); @@ -841,12 +1088,44 @@ pg_wait_sampling_reset_profile(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; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; check_shmem(); + /* + * Check we have the expected number of output arguments. Safety check + */ + switch(api_version) + { + case PGWS_V1_1: + if (rsinfo->expectedDesc->natts != PG_WAIT_SAMPLING_COLS_V1_1) + elog(ERROR, "incorrect number of output arguments"); + break; + case PGWS_V1_2: + if (rsinfo->expectedDesc->natts != PG_WAIT_SAMPLING_COLS_V1_2) + elog(ERROR, "incorrect number of output arguments"); + break; + default: + elog(ERROR, "incorrect number of output arguments"); + } + if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; @@ -857,24 +1136,17 @@ pg_wait_sampling_get_history(PG_FUNCTION_ARGS) /* Receive history from shmq */ history = (History *) palloc0(sizeof(History)); - history->items = (HistoryItem *) receive_array(HISTORY_REQUEST, - sizeof(HistoryItem), &history->count); + history->samples = (Sample *) receive_array(HISTORY_REQUEST, + sizeof(Sample), &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", + tupdesc = CreateTemplateTupleDesc(rsinfo->expectedDesc->natts); + fill_tuple_desc (tupdesc, api_version); + TupleDescInitEntry(tupdesc, (AttrNumber) rsinfo->expectedDesc->natts, "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); @@ -888,32 +1160,19 @@ pg_wait_sampling_get_history(PG_FUNCTION_ARGS) if (history->index < history->count) { HeapTuple tuple; - HistoryItem *item; - Datum values[5]; - bool nulls[5]; - const char *event_type, - *event; + Sample *sample; + Datum values[PG_WAIT_SAMPLING_COLS]; + bool nulls[PG_WAIT_SAMPLING_COLS]; - item = &history->items[history->index]; + sample = &history->samples[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); - 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); + fill_values_and_nulls(values, nulls, *sample, + pgws_history_dimensions, api_version); + values[rsinfo->expectedDesc->natts - 1] = TimestampTzGetDatum(sample->ts); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); history->index++; 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..03ff443 100644 --- a/pg_wait_sampling.h +++ b/pg_wait_sampling.h @@ -17,32 +17,48 @@ #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 -typedef struct -{ - int pid; - uint32 wait_event_info; - uint64 queryId; - uint64 count; -} ProfileItem; +/* Values for sampling dimensions */ +#define PGWS_DIMENSIONS_PID (1 << 0) +#define PGWS_DIMENSIONS_WAIT_EVENT (1 << 1) +#define PGWD_DIMENSIONS_QUERY_ID (1 << 2) +#define PGWS_DIMENSIONS_ROLE_ID (1 << 3) +#define PGWS_DIMENSIONS_DB_ID (1 << 4) +#define PGWS_DIMENSIONS_PARALLEL_LEADER_PID (1 << 5) +#define PGWS_DIMENSIONS_IS_REGULAR_BE (1 << 6) + +#define PGWS_DIMENSIONS_ALL ((int) ~0) +/* ^ all 1 in binary */ +/* + * This can store both profile and history item + */ typedef struct { - int pid; - uint32 wait_event_info; - uint64 queryId; - TimestampTz ts; -} HistoryItem; + /* 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; + /* count is for profile samples, ts is for history samples */ + union + { + uint64 count; + TimestampTz ts; + }; +} Sample; typedef struct { bool wraparound; Size index; Size count; - HistoryItem *items; + Sample *samples; } History; typedef enum @@ -66,6 +82,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; @@ -75,6 +93,8 @@ 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); /* collector.c */ +extern void fill_sample(Sample *sample, 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);