From 650f3fd68ab8d9d11616689bfcf8f6c3dfb772e0 Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Mon, 10 Nov 2025 00:03:41 -0600 Subject: [PATCH v6 1/1] Allow cumulative statistics to serialize auxiliary data to disk. Cumulative Statistics kinds can now write additional per-entry data to the statistics file that doesn't fit in shared memory. This is useful for statistics with variable-length auxiliary data. Three new optional callbacks are added to PgStat_KindInfo: * to_serialized_extra_stats: writes auxiliary data for an entry * from_serialized_extra_stats: reads auxiliary data for an entry * end_extra_stats: performs cleanup after read/write/discard operations All three callbacks must be provided together to ensure the reader consumes exactly what the writer produces. The end_extra_stats callback is invoked after processing all entries of a kind, allowing extensions to close file handles and clean up resources. Tests are also added to test_custom_stats.pl Discussion: https://www.postgresql.org/message-id/flat/CAA5RZ0s9SDOu+Z6veoJCHWk+kDeTktAtC-KY9fQ9Z6BJdDUirQ@mail.gmail.com --- src/backend/utils/activity/pgstat.c | 89 ++++- src/include/utils/pgstat_internal.h | 37 ++ .../test_custom_stats/t/001_custom_stats.pl | 26 +- .../test_custom_var_stats--1.0.sql | 7 +- .../test_custom_stats/test_custom_var_stats.c | 319 +++++++++++++++++- 5 files changed, 457 insertions(+), 21 deletions(-) diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index 8713c7a0483..2e0c1bbb061 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -194,6 +194,7 @@ static void pgstat_build_snapshot(void); static void pgstat_build_snapshot_fixed(PgStat_Kind kind); static inline bool pgstat_is_kind_valid(PgStat_Kind kind); +static inline bool pgstat_check_extra_callbacks(PgStat_Kind kind); /* ---------- @@ -523,6 +524,7 @@ pgstat_discard_stats(void) /* NB: this needs to be done even in single user mode */ + /* First, cleanup the main stats file, PGSTAT_STAT_PERMANENT_FILENAME */ ret = unlink(PGSTAT_STAT_PERMANENT_FILENAME); if (ret != 0) { @@ -544,6 +546,15 @@ pgstat_discard_stats(void) PGSTAT_STAT_PERMANENT_FILENAME))); } + /* Let each stats kind run its cleanup callback, if it provides one */ + for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++) + { + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + if (kind_info && kind_info->end_extra_stats) + kind_info->end_extra_stats(STATS_DISCARD); + } + /* * Reset stats contents. This will set reset timestamps of fixed-numbered * stats to the current time (no variable stats exist). @@ -645,6 +656,13 @@ pgstat_initialize(void) pgstat_attach_shmem(); + /* Check a kind's extra-data callback setup */ + for (PgStat_Kind kind = PGSTAT_KIND_BUILTIN_MIN; kind <= PGSTAT_KIND_BUILTIN_MAX; kind++) + if (!pgstat_check_extra_callbacks(kind)) + ereport(ERROR, + errmsg("incomplete extra serialization callbacks for stats kind %d", + kind)); + pgstat_init_snapshot_fixed(); /* Backend initialization callbacks */ @@ -1432,6 +1450,33 @@ pgstat_is_kind_valid(PgStat_Kind kind) return pgstat_is_kind_builtin(kind) || pgstat_is_kind_custom(kind); } +/* + * Validate that extra stats callbacks are all provided together or not at all. + */ +static inline bool +pgstat_check_extra_callbacks(PgStat_Kind kind) +{ + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + int count = 0; + + /* Custom kind not yet registered, skip validation */ + if (!kind_info) + return true; + + if (kind_info->to_serialized_extra_stats) + count++; + if (kind_info->from_serialized_extra_stats) + count++; + if (kind_info->end_extra_stats) + count++; + + /* Either all three callbacks must be provided, or none */ + if (count != 0 && count != 3) + return false; + + return true; +} + const PgStat_KindInfo * pgstat_get_kind_info(PgStat_Kind kind) { @@ -1525,6 +1570,13 @@ pgstat_register_kind(PgStat_Kind kind, const PgStat_KindInfo *kind_info) errdetail("Existing cumulative statistics with ID %u has the same name.", existing_kind))); } + /* Check a kind's extra-data callback setup */ + if (!pgstat_check_extra_callbacks(kind)) + ereport(ERROR, + (errmsg("incomplete serialization callbacks for statistics kind \"%s\"", + kind_info->name), + errdetail("callbacks to_serialized_extra_stats, from_serialized_extra_stats, and end_extra_stats must be provided together."))); + /* Register it */ pgstat_kind_custom_infos[idx] = kind_info; ereport(LOG, @@ -1702,6 +1754,9 @@ pgstat_write_statsfile(void) pgstat_write_chunk(fpout, pgstat_get_entry_data(ps->key.kind, shstats), pgstat_get_entry_len(ps->key.kind)); + + if (kind_info->to_serialized_extra_stats) + kind_info->to_serialized_extra_stats(&ps->key, shstats, fpout); } dshash_seq_term(&hstat); @@ -1734,6 +1789,15 @@ pgstat_write_statsfile(void) /* durable_rename already emitted log message */ unlink(tmpfile); } + + /* Now, allow kinds to finalize the writes for the extra files */ + for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++) + { + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + if (kind_info && kind_info->end_extra_stats) + kind_info->end_extra_stats(STATS_WRITE); + } } /* helper for pgstat_read_statsfile() */ @@ -1871,6 +1935,7 @@ pgstat_read_statsfile(void) PgStat_HashKey key; PgStatShared_HashEntry *p; PgStatShared_Common *header; + const PgStat_KindInfo *kind_info = NULL; CHECK_FOR_INTERRUPTS(); @@ -1891,7 +1956,8 @@ pgstat_read_statsfile(void) goto error; } - if (!pgstat_get_kind_info(key.kind)) + kind_info = pgstat_get_kind_info(key.kind); + if (!kind_info) { elog(WARNING, "could not find information of kind for entry %u/%u/%" PRIu64 " of type %c", key.kind, key.dboid, @@ -1902,7 +1968,6 @@ pgstat_read_statsfile(void) else { /* stats entry identified by name on disk (e.g. slots) */ - const PgStat_KindInfo *kind_info = NULL; PgStat_Kind kind; NameData name; @@ -1996,6 +2061,16 @@ pgstat_read_statsfile(void) goto error; } + if (kind_info->from_serialized_extra_stats) + { + if (!kind_info->from_serialized_extra_stats(&key, header, fpin)) + { + elog(WARNING, "could not read extra stats for entry %u/%u/%" PRIu64, + key.kind, key.dboid, key.objid); + goto error; + } + } + break; } case PGSTAT_FILE_ENTRY_END: @@ -2019,11 +2094,21 @@ pgstat_read_statsfile(void) } done: + /* First, cleanup the main stats file, PGSTAT_STAT_PERMANENT_FILENAME */ FreeFile(fpin); elog(DEBUG2, "removing permanent stats file \"%s\"", statfile); unlink(statfile); + /* Let each stats kind run its cleanup callback, if it provides one */ + for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++) + { + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + if (kind_info && kind_info->end_extra_stats) + kind_info->end_extra_stats(STATS_READ); + } + return; error: diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h index ca1ba6420ca..48b40816570 100644 --- a/src/include/utils/pgstat_internal.h +++ b/src/include/utils/pgstat_internal.h @@ -63,6 +63,20 @@ typedef struct PgStat_HashKey * identifier. */ } PgStat_HashKey; +/* + * Tracks if the stats file is being read, written or discarded. + * + * These states allow plugins that create extra statistics files + * to determine the current operation and perform any necessary + * file cleanup. + */ +typedef enum PgStat_StatsFileOp +{ + STATS_WRITE, + STATS_READ, + STATS_DISCARD, +} PgStat_StatsFileOp; + /* * PgStat_HashKey should not have any padding. Checking that the structure * size matches with the sum of each field is a check simple enough to @@ -303,6 +317,29 @@ typedef struct PgStat_KindInfo const PgStatShared_Common *header, NameData *name); bool (*from_serialized_name) (const NameData *name, PgStat_HashKey *key); + /* + * Optional callbacks for kinds that write additional per-entry data to + * the stats file. If any of these callbacks are provided, all three must + * be provided to ensure that the reader consumes exactly the data written + * by the writer. + * + * to_serialized_extra_stats: write extra data for an entry. + * + * from_serialized_extra_stats: read the extra data for an entry. Returns + * true on success, false on read error. + * + * end_extra_stats: invoked once per operation (read, write, discard) + * after all entries of this kind have been processed. + * + * Note: statfile is a pointer to the main stats file, + * PGSTAT_STAT_PERMANENT_FILENAME. + */ + void (*to_serialized_extra_stats) (const PgStat_HashKey *key, + const PgStatShared_Common *header, FILE *statfile); + bool (*from_serialized_extra_stats) (const PgStat_HashKey *key, + const PgStatShared_Common *header, FILE *statfile); + void (*end_extra_stats) (PgStat_StatsFileOp status); + /* * For fixed-numbered statistics: Initialize shared memory state. * diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index e528595cfb0..b3b25819411 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -29,13 +29,13 @@ $node->safe_psql('postgres', q(CREATE EXTENSION test_custom_fixed_stats)); # Create entries for variable-sized stats. $node->safe_psql('postgres', - q(select test_custom_stats_var_create('entry1'))); + q(select test_custom_stats_var_create('entry1', 'Test entry 1'))); $node->safe_psql('postgres', - q(select test_custom_stats_var_create('entry2'))); + q(select test_custom_stats_var_create('entry2', 'Test entry 2'))); $node->safe_psql('postgres', - q(select test_custom_stats_var_create('entry3'))); + q(select test_custom_stats_var_create('entry3', 'Test entry 3'))); $node->safe_psql('postgres', - q(select test_custom_stats_var_create('entry4'))); + q(select test_custom_stats_var_create('entry4', 'Test entry 4'))); # Update counters: entry1=2, entry2=3, entry3=2, entry4=3, fixed=3 $node->safe_psql('postgres', @@ -65,16 +65,20 @@ $node->safe_psql('postgres', q(select test_custom_stats_fixed_update())); # Test data reports. my $result = $node->safe_psql('postgres', q(select * from test_custom_stats_var_report('entry1'))); -is($result, "entry1|2", "report for variable-sized data of entry1"); +is($result, "entry1|2|Test entry 1", "report for variable-sized data of entry1"); + $result = $node->safe_psql('postgres', q(select * from test_custom_stats_var_report('entry2'))); -is($result, "entry2|3", "report for variable-sized data of entry2"); +is($result, "entry2|3|Test entry 2", "report for variable-sized data of entry2"); + $result = $node->safe_psql('postgres', q(select * from test_custom_stats_var_report('entry3'))); -is($result, "entry3|2", "report for variable-sized data of entry3"); +is($result, "entry3|2|Test entry 3", "report for variable-sized data of entry3"); + $result = $node->safe_psql('postgres', q(select * from test_custom_stats_var_report('entry4'))); -is($result, "entry4|3", "report for variable-sized data of entry4"); +is($result, "entry4|3|Test entry 4", "report for variable-sized data of entry4"); + $result = $node->safe_psql('postgres', q(select * from test_custom_stats_fixed_report())); is($result, "3|", "report for fixed-sized stats"); @@ -97,7 +101,11 @@ $node->start(); $result = $node->safe_psql('postgres', q(select * from test_custom_stats_var_report('entry1'))); -is($result, "entry1|2", "variable-sized stats persist after clean restart"); +is($result, "entry1|2|Test entry 1", "variable-sized stats persist after clean restart"); + +$result = $node->safe_psql('postgres', q(select * from test_custom_stats_var_report('entry2'))); +is($result, "entry2|3|Test entry 2", "variable-sized stats persist after clean restart"); + $result = $node->safe_psql('postgres', q(select * from test_custom_stats_fixed_report())); is($result, "3|", "fixed-sized stats persist after clean restart"); diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql index d5f82b5d546..5ed8cfc2dcf 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql @@ -3,7 +3,7 @@ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION test_custom_var_stats" to load this file. \quit -CREATE FUNCTION test_custom_stats_var_create(IN name TEXT) +CREATE FUNCTION test_custom_stats_var_create(IN name TEXT, in description TEXT) RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_var_create' LANGUAGE C STRICT PARALLEL UNSAFE; @@ -18,8 +18,9 @@ RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_var_drop' LANGUAGE C STRICT PARALLEL UNSAFE; - -CREATE FUNCTION test_custom_stats_var_report(INOUT name TEXT, OUT calls BIGINT) +CREATE FUNCTION test_custom_stats_var_report(INOUT name TEXT, + OUT calls BIGINT, + OUT description TEXT) RETURNS SETOF record AS 'MODULE_PATHNAME', 'test_custom_stats_var_report' LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index d4905ab4ee9..da57fb192ab 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -14,6 +14,7 @@ #include "common/hashfn.h" #include "funcapi.h" +#include "storage/dsm_registry.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" @@ -32,6 +33,9 @@ PG_MODULE_MAGIC_EXT( */ #define PGSTAT_KIND_TEST_CUSTOM_VAR_STATS 25 +/* File paths for extra statistics data serialization */ +#define TEST_CUSTOM_EXTRA_DATA_DESC "pg_stat/test_custom_var_stats_desc.stats" + /* * Hash statistic name to generate entry index for pgstat lookup. */ @@ -53,8 +57,23 @@ typedef struct PgStatShared_CustomVarEntry { PgStatShared_Common header; /* standard pgstat entry header */ PgStat_StatCustomVarEntry stats; /* custom statistics data */ + dsa_pointer description; /* extra statistics data */ } PgStatShared_CustomVarEntry; +/*-------------------------------------------------------------------------- + * Global Variables + *-------------------------------------------------------------------------- + */ + +/* File handle for extra statistics data serialization */ +static FILE *fd_description = NULL; + +/* Current write offset in fd_description file */ +static long fd_description_offset = 0; + +/* DSA area for storing variable-length description strings */ +dsa_area *custom_stats_description_dsa = NULL; + /*-------------------------------------------------------------------------- * Function prototypes *-------------------------------------------------------------------------- @@ -64,6 +83,17 @@ typedef struct PgStatShared_CustomVarEntry static bool test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait); +/* Serialization callback: serialize extra statistics data */ +static void test_custom_var_stats_serialize(const PgStat_HashKey *key, + const PgStatShared_Common *header, FILE *statfile); + +/* Deserialization callback: deserialize extra statistics data */ +static bool test_custom_var_stats_deserialize(const PgStat_HashKey *key, + const PgStatShared_Common *header, FILE *statfile); + +/* Cleanup callback: end of statistics file operations */ +static void test_custom_var_stats_file_cleanup(PgStat_StatsFileOp status); + /*-------------------------------------------------------------------------- * Custom kind configuration *-------------------------------------------------------------------------- @@ -80,6 +110,9 @@ static const PgStat_KindInfo custom_stats = { .shared_data_len = sizeof(((PgStatShared_CustomVarEntry *) 0)->stats), .pending_size = sizeof(PgStat_StatCustomVarEntry), .flush_pending_cb = test_custom_stats_var_flush_pending_cb, + .to_serialized_extra_stats = test_custom_var_stats_serialize, + .from_serialized_extra_stats = test_custom_var_stats_deserialize, + .end_extra_stats = test_custom_var_stats_file_cleanup, }; /*-------------------------------------------------------------------------- @@ -132,6 +165,232 @@ test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait) return true; } +/* + * test_custom_var_stats_serialize() - + * + * Serialize extra data (descriptions) for custom statistics entries to + * the statistics file. Called during statistics file writing to preserve + * description strings across restarts. + */ +static void +test_custom_var_stats_serialize(const PgStat_HashKey *key, + const PgStatShared_Common *header, FILE *statfile) +{ + char *description; + size_t len; + long offset; + PgStatShared_CustomVarEntry *entry = (PgStatShared_CustomVarEntry *) header; + bool found; + + if (!custom_stats_description_dsa) + custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found); + + /* Open statistics file for writing if not already open */ + if (!fd_description) + { + fd_description = AllocateFile(TEST_CUSTOM_EXTRA_DATA_DESC, PG_BINARY_W); + if (fd_description == NULL) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not open statistics file \"%s\" for writing: %m", + TEST_CUSTOM_EXTRA_DATA_DESC))); + len = 0; + offset = 0; + fwrite(&len, sizeof(len), 1, statfile); + fwrite(&offset, sizeof(offset), 1, statfile); + return; + } + fd_description_offset = 0; + } + + /* Handle entries without descriptions */ + if (!DsaPointerIsValid(entry->description) || !custom_stats_description_dsa) + { + len = 0; + offset = 0; + fwrite(&len, sizeof(len), 1, statfile); + fwrite(&offset, sizeof(offset), 1, statfile); + return; + } + + /* Get current offset in fd_description */ + offset = fd_description_offset; + + /* Retrieve description from DSA and write to fd_description */ + description = dsa_get_address(custom_stats_description_dsa, entry->description); + len = strlen(description) + 1; + fwrite(description, 1, len, fd_description); + fd_description_offset += len; + + /* Write length and offset to statfile */ + fwrite(&len, sizeof(len), 1, statfile); + fwrite(&offset, sizeof(offset), 1, statfile); +} + +/* + * test_custom_var_stats_deserialize() - + * + * Deserialize extra data (descriptions) for custom statistics entries from + * the statistics file. Called during statistics file reading to restore + * description strings after a restart. + */ +static bool +test_custom_var_stats_deserialize(const PgStat_HashKey *key, + const PgStatShared_Common *header, FILE *statfile) +{ + PgStatShared_CustomVarEntry *entry; + dsa_pointer dp; + size_t len; + long offset; + char *buffer; + bool found; + + /* Read length and offset from statfile */ + if (fread(&len, sizeof(len), 1, statfile) != 1 || + fread(&offset, sizeof(offset), 1, statfile) != 1) + { + elog(WARNING, "failed to read description metadata from statistics file"); + return false; + } + + entry = (PgStatShared_CustomVarEntry *) header; + + /* Handle empty descriptions */ + if (len == 0) + { + entry->description = InvalidDsaPointer; + return true; + } + + /* Initialize DSA if needed */ + if (!custom_stats_description_dsa) + custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found); + + if (!custom_stats_description_dsa) + { + elog(WARNING, "could not access DSA for custom statistics descriptions"); + return false; + } + + /* Open statistics file for reading if not already open */ + if (!fd_description) + { + fd_description = AllocateFile(TEST_CUSTOM_EXTRA_DATA_DESC, PG_BINARY_R); + if (fd_description == NULL) + { + if (errno != ENOENT) + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not open statistics file \"%s\" for reading: %m", + TEST_CUSTOM_EXTRA_DATA_DESC))); + pgstat_reset_of_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS); + return false; + } + } + + /* Seek to the offset and read description */ + if (fseek(fd_description, offset, SEEK_SET) != 0) + { + elog(WARNING, "failed to seek to offset %ld in description file", offset); + return false; + } + + buffer = palloc(len); + if (fread(buffer, 1, len, fd_description) != len) + { + pfree(buffer); + elog(WARNING, "failed to read description from file"); + return false; + } + + /* Allocate space in DSA and copy the description */ + dp = dsa_allocate(custom_stats_description_dsa, len); + memcpy(dsa_get_address(custom_stats_description_dsa, dp), buffer, len); + entry->description = dp; + + pfree(buffer); + + return true; +} + +/* + * test_custom_var_stats_file_cleanup() - + * + * Cleanup function called at the end of statistics file operations. + * Handles closing files and cleanup based on the operation type. + */ +static void +test_custom_var_stats_file_cleanup(PgStat_StatsFileOp status) +{ + switch (status) + { + case STATS_WRITE: + if (!fd_description) + return; + + fd_description_offset = 0; + + /* Check for write errors and cleanup if necessary */ + if (ferror(fd_description)) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write statistics file \"%s\": %m", + TEST_CUSTOM_EXTRA_DATA_DESC))); + FreeFile(fd_description); + unlink(TEST_CUSTOM_EXTRA_DATA_DESC); + } + else if (FreeFile(fd_description) < 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not close statistics file \"%s\": %m", + TEST_CUSTOM_EXTRA_DATA_DESC))); + unlink(TEST_CUSTOM_EXTRA_DATA_DESC); + } + break; + + case STATS_READ: + if (fd_description) + FreeFile(fd_description); + + /* Remove the temporary statistics file after reading */ + elog(DEBUG2, "removing statistics file \"%s\"", TEST_CUSTOM_EXTRA_DATA_DESC); + unlink(TEST_CUSTOM_EXTRA_DATA_DESC); + break; + + case STATS_DISCARD: + { + int ret; + + /* Attempt to remove the statistics file */ + ret = unlink(TEST_CUSTOM_EXTRA_DATA_DESC); + if (ret != 0) + { + if (errno == ENOENT) + elog(LOG, + "didn't need to unlink permanent stats file \"%s\" - didn't exist", + TEST_CUSTOM_EXTRA_DATA_DESC); + else + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not unlink permanent statistics file \"%s\": %m", + TEST_CUSTOM_EXTRA_DATA_DESC))); + } + else + { + ereport(LOG, + (errmsg_internal("unlinked permanent statistics file \"%s\"", + TEST_CUSTOM_EXTRA_DATA_DESC))); + } + } + break; + } + + fd_description = NULL; +} + /*-------------------------------------------------------------------------- * Helper functions *-------------------------------------------------------------------------- @@ -162,8 +421,7 @@ test_custom_stats_var_fetch_entry(const char *stat_name) * test_custom_stats_var_create * Create new custom statistic entry * - * Initializes a zero-valued statistics entry in shared memory. - * Validates name length against NAMEDATALEN limit. + * Initializes a statistics entry with the given name and description. */ PG_FUNCTION_INFO_V1(test_custom_stats_var_create); Datum @@ -172,6 +430,9 @@ test_custom_stats_var_create(PG_FUNCTION_ARGS) PgStat_EntryRef *entry_ref; PgStatShared_CustomVarEntry *shared_entry; char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *description = text_to_cstring(PG_GETARG_TEXT_PP(1)); + dsa_pointer dp = InvalidDsaPointer; + bool found; /* Validate name length first */ if (strlen(stat_name) >= NAMEDATALEN) @@ -180,6 +441,20 @@ test_custom_stats_var_create(PG_FUNCTION_ARGS) errmsg("custom statistic name \"%s\" is too long", stat_name), errdetail("Name must be less than %d characters.", NAMEDATALEN))); + /* Initialize DSA and description provided */ + if (!custom_stats_description_dsa) + custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found); + + if (!custom_stats_description_dsa) + ereport(ERROR, + (errmsg("could not access DSA for custom statistics descriptions"))); + + /* Allocate space in DSA and copy description */ + dp = dsa_allocate(custom_stats_description_dsa, strlen(description) + 1); + memcpy(dsa_get_address(custom_stats_description_dsa, dp), + description, + strlen(description) + 1); + /* Create or get existing entry */ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid, PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), true); @@ -192,6 +467,9 @@ test_custom_stats_var_create(PG_FUNCTION_ARGS) /* Zero-initialize statistics */ memset(&shared_entry->stats, 0, sizeof(shared_entry->stats)); + /* Store description pointer */ + shared_entry->description = dp; + pgstat_unlock_entry(entry_ref); PG_RETURN_VOID(); @@ -226,8 +504,7 @@ test_custom_stats_var_update(PG_FUNCTION_ARGS) * test_custom_stats_var_drop * Remove custom statistic entry * - * Drops the named statistic from shared memory and requests - * garbage collection if needed. + * Drops the named statistic from shared memory. */ PG_FUNCTION_INFO_V1(test_custom_stats_var_drop); Datum @@ -247,7 +524,7 @@ test_custom_stats_var_drop(PG_FUNCTION_ARGS) * test_custom_stats_var_report * Retrieve custom statistic values * - * Returns single row with statistic name and call count if the + * Returns single row with statistic name, call count, and description if the * statistic exists, otherwise returns no rows. */ PG_FUNCTION_INFO_V1(test_custom_stats_var_report); @@ -281,9 +558,13 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS) if (funcctx->call_cntr < funcctx->max_calls) { - Datum values[2]; - bool nulls[2] = {false, false}; + Datum values[3]; + bool nulls[3] = {false, false, false}; HeapTuple tuple; + PgStat_EntryRef *entry_ref; + PgStatShared_CustomVarEntry *shared_entry; + char *description = NULL; + bool found; stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); stat_entry = test_custom_stats_var_fetch_entry(stat_name); @@ -291,9 +572,33 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS) /* Return row only if entry exists */ if (stat_entry) { + /* Get entry ref to access shared entry */ + entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid, + PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), false, NULL); + + if (entry_ref) + { + shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats; + + /* Get description from DSA if available */ + if (DsaPointerIsValid(shared_entry->description)) + { + if (!custom_stats_description_dsa) + custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found); + + if (custom_stats_description_dsa) + description = dsa_get_address(custom_stats_description_dsa, shared_entry->description); + } + } + values[0] = PointerGetDatum(cstring_to_text(stat_name)); values[1] = Int64GetDatum(stat_entry->numcalls); + if (description) + values[2] = PointerGetDatum(cstring_to_text(description)); + else + nulls[2] = true; + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); } -- 2.43.0