v6-0001-Allow-cumulative-statistics-to-serialize-auxiliar.patch
application/octet-stream
Filename: v6-0001-Allow-cumulative-statistics-to-serialize-auxiliar.patch
Type: application/octet-stream
Part: 0
From 650f3fd68ab8d9d11616689bfcf8f6c3dfb772e0 Mon Sep 17 00:00:00 2001
From: Sami Imseih <simseih@amazon.com>
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