0001-Add-callback-support-for-custom-statistics-extra-dat.patch
application/octet-stream
Filename: 0001-Add-callback-support-for-custom-statistics-extra-dat.patch
Type: application/octet-stream
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch 0001
Subject: Add callback support for custom statistics extra data serialization
| File | + | − |
|---|---|---|
| src/backend/utils/activity/pgstat.c | 295 | 96 |
| src/include/utils/pgstat_internal.h | 18 | 0 |
From 5cd825f048c4104231d2ce94b87d3e7d5a9a1506 Mon Sep 17 00:00:00 2001
From: Ubuntu <ubuntu@ip-172-31-46-230.ec2.internal>
Date: Wed, 22 Oct 2025 18:45:43 +0000
Subject: [PATCH 1/2] Add callback support for custom statistics extra data
serialization
Allow custom statistics kinds to serialize additional per-entry data
beyond the standard statistics entries. Custom kinds can register
to_serialized_extra and from_serialized_extra callbacks to write
and read extra data to separate files (pgstat.<kind>.stat).
The callbacks have access to the file pointer and the extension
that registers this custom statistic can write/read the data as it
understands it. The core pgstat infrastructure manages the files as
it does with pgstat.stat.
The callbacks are optional and only valid for custom,
variable-numbered statistics kinds. Using separate files keeps the
main statistics file format stable while enabling more complex
custom statistics.
This will allow extensions like pg_stat_statements to use custom
kinds and track additional data per entry such as query text, which
can be stored in a dsa_pointer. Using these callbacks, the
dsa_pointer can be serialized and deserialized across server
restarts.
---
src/backend/utils/activity/pgstat.c | 391 +++++++++++++++++++++-------
src/include/utils/pgstat_internal.h | 18 ++
2 files changed, 313 insertions(+), 96 deletions(-)
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 7ef06150df7..d4a724d23bd 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -146,6 +146,21 @@
#define PGSTAT_FILE_ENTRY_HASH 'S' /* stats entry identified by
* PgStat_HashKey */
+/* ---------
+ * Limits for on-disk stats files.
+ *
+ * We maintain separate files for:
+ * - One built-in stats file (all standard PostgreSQL statistics)
+ * - One file per custom stats kind that uses extra serialization callbacks
+ *
+ * PGSTAT_BUILTIN_FILE is the array index for the built-in stats file.
+ * Custom stats files occupy indices 1 through PGSTAT_MAX_CUSTOM_FILES.
+ * ---------
+ */
+#define PGSTAT_MAX_CUSTOM_FILES PGSTAT_KIND_CUSTOM_MAX - PGSTAT_KIND_CUSTOM_MIN + 1
+#define PGSTAT_MAX_FILES PGSTAT_MAX_CUSTOM_FILES + 1
+#define PGSTAT_BUILTIN_FILE PGSTAT_KIND_MIN - 1
+
/* hash table for statistics snapshots entry */
typedef struct PgStat_SnapshotEntry
{
@@ -174,6 +189,16 @@ typedef struct PgStat_SnapshotEntry
#define SH_DECLARE
#include "lib/simplehash.h"
+/*
+ * Macros for custom stats file paths.
+ * Each custom stats kind with extra serialization data gets its own file
+ * in the format pgstat.<kind>.{tmp,stat}
+ */
+#define CUSTOM_TMPFILE_PATH(kind) \
+ psprintf(PGSTAT_STAT_PERMANENT_DIRECTORY "/pgstat.%d.tmp", (kind))
+
+#define CUSTOM_STATFILE_PATH(kind) \
+ psprintf(PGSTAT_STAT_PERMANENT_DIRECTORY "/pgstat.%d.stat", (kind))
/* ----------
* Local function forward declarations
@@ -1525,6 +1550,31 @@ pgstat_register_kind(PgStat_Kind kind, const PgStat_KindInfo *kind_info)
errdetail("Existing cumulative statistics with ID %u has the same name.", existing_kind)));
}
+ /*
+ * Ensure that both serialization and de-serialization callbacks are
+ * either registered together or not at all, and only for custom,
+ * variable-numbered stats.
+ */
+ if ((kind_info->to_serialized_extra && !kind_info->from_serialized_extra) ||
+ (!kind_info->to_serialized_extra && kind_info->from_serialized_extra))
+ {
+ ereport(ERROR,
+ (errmsg("invalid custom statistics callbacks for \"%s\" (ID %u)",
+ kind_info->name, kind),
+ errdetail("Both to_serialized_extra and from_serialized_extra "
+ "must either be set or unset.")));
+ }
+
+ if (kind_info->to_serialized_extra != NULL &&
+ (!pgstat_is_kind_custom(kind) || kind_info->fixed_amount))
+ {
+ ereport(ERROR,
+ (errmsg("invalid custom statistics callbacks for \"%s\" (ID %u)",
+ kind_info->name, kind),
+ errdetail("to_serialized_extra and from_serialized_extra callbacks are only allowed for "
+ "custom, variable-numbered statistics kinds.")));
+ }
+
/* Register it */
pgstat_kind_custom_infos[idx] = kind_info;
ereport(LOG,
@@ -1551,9 +1601,65 @@ pgstat_assert_is_up(void)
* ------------------------------------------------------------
*/
-/* helpers for pgstat_write_statsfile() */
+/* helpers for pgstat_read|write_statsfile() */
static void
-write_chunk(FILE *fpout, void *ptr, size_t len)
+pgstat_close_files(FILE **fp, int nfiles)
+{
+ /*
+ * Free all the allocated files.
+ */
+ for (int i = 0; i < nfiles; i++)
+ {
+ if (fp[i])
+ {
+ FreeFile(fp[i]);
+ fp[i] = NULL;
+ }
+ }
+}
+
+static bool
+pgstat_get_filenames_for_kind(const PgStat_Kind kind,
+ char **tmpfile, char **statfile)
+{
+ /*
+ * If this is a custom kind that serializes extra data, or the built-in
+ * pgstats file, return the appropriate name.
+ */
+ if (kind >= PGSTAT_KIND_CUSTOM_MIN)
+ {
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (!kind_info || !kind_info->from_serialized_extra)
+ return false;
+
+ *tmpfile = CUSTOM_TMPFILE_PATH(kind);
+ *statfile = CUSTOM_STATFILE_PATH(kind);
+ return true;
+ }
+ else
+ {
+ *tmpfile = pstrdup(PGSTAT_STAT_PERMANENT_TMPFILE);
+ *statfile = pstrdup(PGSTAT_STAT_PERMANENT_FILENAME);
+ return true;
+ }
+}
+
+static void
+pgstat_report_statfile_error(PgStat_Kind kind, const char *statfile)
+{
+ ereport(LOG,
+ (errmsg("corrupted statistics file \"%s\"", statfile)));
+
+ if (kind == PGSTAT_BUILTIN_FILE)
+ pgstat_reset_after_failure();
+ else
+ pgstat_reset_of_kind(kind);
+}
+
+/* helpers for pgstat_write_statsfile() */
+void
+pgstat_write_chunk(FILE *fpout, void *ptr, size_t len)
{
int rc;
@@ -1563,7 +1669,16 @@ write_chunk(FILE *fpout, void *ptr, size_t len)
(void) rc;
}
-#define write_chunk_s(fpout, ptr) write_chunk(fpout, ptr, sizeof(*ptr))
+/* helpers for pgstat_read_statsfile() */
+static void
+pgstat_close_statfile(FILE *fp, const char *statfile)
+{
+ if (fp)
+ FreeFile(fp);
+
+ elog(DEBUG2, "removing permanent stats file \"%s\"", statfile);
+ unlink(statfile);
+}
/*
* This function is called in the last process that is accessing the shared
@@ -1572,10 +1687,8 @@ write_chunk(FILE *fpout, void *ptr, size_t len)
static void
pgstat_write_statsfile(void)
{
- FILE *fpout;
+ FILE *fpout[PGSTAT_MAX_FILES] = {NULL};
int32 format_id;
- const char *tmpfile = PGSTAT_STAT_PERMANENT_TMPFILE;
- const char *statfile = PGSTAT_STAT_PERMANENT_FILENAME;
dshash_seq_status hstat;
PgStatShared_HashEntry *ps;
@@ -1587,26 +1700,44 @@ pgstat_write_statsfile(void)
/* we're shutting down, so it's ok to just override this */
pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_NONE;
- elog(DEBUG2, "writing stats file \"%s\"", statfile);
-
/*
- * Open the statistics temp file to write out the current values.
+ * Open temporary statistics files: one for built-in stats (idx=0), and
+ * one for each custom kind with extra serialization callbacks.
*/
- fpout = AllocateFile(tmpfile, PG_BINARY_W);
- if (fpout == NULL)
+ for (int idx = 0; idx <= PGSTAT_MAX_CUSTOM_FILES; idx++)
{
- ereport(LOG,
- (errcode_for_file_access(),
- errmsg("could not open temporary statistics file \"%s\": %m",
- tmpfile)));
- return;
+ char *tmpfile;
+ char *statfile;
+
+ if (!pgstat_get_filenames_for_kind(PGSTAT_KIND_CUSTOM_MIN + idx - 1,
+ &tmpfile, &statfile))
+ continue;
+
+ elog(DEBUG2, "writing stats file \"%s\"", statfile);
+
+ fpout[idx] = AllocateFile(tmpfile, PG_BINARY_W);
+
+ if (fpout[idx] == NULL)
+ {
+ pgstat_close_files(fpout, PGSTAT_MAX_FILES);
+ ereport(LOG,
+ (errcode_for_file_access(),
+ errmsg("could not open temporary statistics file \"%s\": %m",
+ tmpfile)));
+ pfree(tmpfile);
+ pfree(statfile);
+ return;
+ }
+
+ pfree(tmpfile);
+ pfree(statfile);
}
/*
* Write the file header --- currently just a format ID.
*/
format_id = PGSTAT_FILE_FORMAT_ID;
- write_chunk_s(fpout, &format_id);
+ pgstat_write_chunk_s(fpout[PGSTAT_BUILTIN_FILE], &format_id);
/* Write various stats structs for fixed number of objects */
for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
@@ -1630,9 +1761,9 @@ pgstat_write_statsfile(void)
else
ptr = pgStatLocal.snapshot.custom_data[kind - PGSTAT_KIND_CUSTOM_MIN];
- fputc(PGSTAT_FILE_ENTRY_FIXED, fpout);
- write_chunk_s(fpout, &kind);
- write_chunk(fpout, ptr, info->shared_data_len);
+ fputc(PGSTAT_FILE_ENTRY_FIXED, fpout[PGSTAT_BUILTIN_FILE]);
+ pgstat_write_chunk_s(fpout[PGSTAT_BUILTIN_FILE], &kind);
+ pgstat_write_chunk(fpout[PGSTAT_BUILTIN_FILE], ptr, info->shared_data_len);
}
/*
@@ -1685,8 +1816,8 @@ pgstat_write_statsfile(void)
if (!kind_info->to_serialized_name)
{
/* normal stats entry, identified by PgStat_HashKey */
- fputc(PGSTAT_FILE_ENTRY_HASH, fpout);
- write_chunk_s(fpout, &ps->key);
+ fputc(PGSTAT_FILE_ENTRY_HASH, fpout[PGSTAT_BUILTIN_FILE]);
+ pgstat_write_chunk_s(fpout[PGSTAT_BUILTIN_FILE], &ps->key);
}
else
{
@@ -1695,58 +1826,80 @@ pgstat_write_statsfile(void)
kind_info->to_serialized_name(&ps->key, shstats, &name);
- fputc(PGSTAT_FILE_ENTRY_NAME, fpout);
- write_chunk_s(fpout, &ps->key.kind);
- write_chunk_s(fpout, &name);
+ fputc(PGSTAT_FILE_ENTRY_NAME, fpout[PGSTAT_BUILTIN_FILE]);
+ pgstat_write_chunk_s(fpout[PGSTAT_BUILTIN_FILE], &ps->key.kind);
+ pgstat_write_chunk_s(fpout[PGSTAT_BUILTIN_FILE], &name);
}
/* Write except the header part of the entry */
- write_chunk(fpout,
- pgstat_get_entry_data(ps->key.kind, shstats),
- pgstat_get_entry_len(ps->key.kind));
+ pgstat_write_chunk(fpout[PGSTAT_BUILTIN_FILE],
+ pgstat_get_entry_data(ps->key.kind, shstats),
+ pgstat_get_entry_len(ps->key.kind));
+
+ /* A plug-in is saving extra data */
+ if (kind_info->to_serialized_extra)
+ {
+ FILE *fp = fpout[(ps->key.kind - PGSTAT_KIND_CUSTOM_MIN) + 1];
+
+ Assert(fp);
+
+ kind_info->to_serialized_extra(&ps->key, shstats, fp);
+ }
}
dshash_seq_term(&hstat);
/*
* No more output to be done. Close the temp file and replace the old
- * pgstat.stat with it. The ferror() check replaces testing for error
- * after each individual fputc or fwrite (in write_chunk()) above.
+ * pgstat.stat or custom stats file with it. The ferror() check replaces
+ * testing for errors after each individual fputc or fwrite (in
+ * pgstat_write_chunk()) above.
*/
- fputc(PGSTAT_FILE_ENTRY_END, fpout);
+ fputc(PGSTAT_FILE_ENTRY_END, fpout[PGSTAT_BUILTIN_FILE]);
- if (ferror(fpout))
+ for (int idx = 0; idx <= PGSTAT_MAX_CUSTOM_FILES; idx++)
{
- ereport(LOG,
- (errcode_for_file_access(),
- errmsg("could not write temporary statistics file \"%s\": %m",
- tmpfile)));
- FreeFile(fpout);
- unlink(tmpfile);
- }
- else if (FreeFile(fpout) < 0)
- {
- ereport(LOG,
- (errcode_for_file_access(),
- errmsg("could not close temporary statistics file \"%s\": %m",
- tmpfile)));
- unlink(tmpfile);
- }
- else if (durable_rename(tmpfile, statfile, LOG) < 0)
- {
- /* durable_rename already emitted log message */
- unlink(tmpfile);
+ char *tmpfile;
+ char *statfile;
+
+ if (!pgstat_get_filenames_for_kind(PGSTAT_KIND_CUSTOM_MIN + idx - 1,
+ &tmpfile, &statfile))
+ continue;
+
+ if (ferror(fpout[idx]))
+ {
+ ereport(LOG,
+ (errcode_for_file_access(),
+ errmsg("could not write temporary statistics file \"%s\": %m",
+ tmpfile)));
+ FreeFile(fpout[idx]);
+ unlink(tmpfile);
+ }
+ else if (FreeFile(fpout[idx]) < 0)
+ {
+ ereport(LOG,
+ (errcode_for_file_access(),
+ errmsg("could not close temporary statistics file \"%s\": %m",
+ tmpfile)));
+ unlink(tmpfile);
+ }
+ else if (durable_rename(tmpfile, statfile, LOG) < 0)
+ {
+ /* durable_rename already emitted log message */
+ unlink(tmpfile);
+ }
+
+ pfree(tmpfile);
+ pfree(statfile);
}
}
/* helpers for pgstat_read_statsfile() */
-static bool
-read_chunk(FILE *fpin, void *ptr, size_t len)
+bool
+pgstat_read_chunk(FILE *fpin, void *ptr, size_t len)
{
return fread(ptr, 1, len, fpin) == len;
}
-#define read_chunk_s(fpin, ptr) read_chunk(fpin, ptr, sizeof(*ptr))
-
/*
* Reads in existing statistics file into memory.
*
@@ -1756,7 +1909,7 @@ read_chunk(FILE *fpin, void *ptr, size_t len)
static void
pgstat_read_statsfile(void)
{
- FILE *fpin;
+ FILE *fpin[PGSTAT_MAX_FILES] = {NULL};
int32 format_id;
bool found;
const char *statfile = PGSTAT_STAT_PERMANENT_FILENAME;
@@ -1765,32 +1918,48 @@ pgstat_read_statsfile(void)
/* shouldn't be called from postmaster */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
- elog(DEBUG2, "reading stats file \"%s\"", statfile);
-
- /*
- * Try to open the stats file. If it doesn't exist, the backends simply
- * returns zero for anything and statistics simply starts from scratch
- * with empty counters.
- *
- * ENOENT is a possibility if stats collection was previously disabled or
- * has not yet written the stats file for the first time. Any other
- * failure condition is suspicious.
- */
- if ((fpin = AllocateFile(statfile, PG_BINARY_R)) == NULL)
+ for (int idx = 0; idx <= PGSTAT_MAX_CUSTOM_FILES; idx++)
{
- if (errno != ENOENT)
- ereport(LOG,
- (errcode_for_file_access(),
- errmsg("could not open statistics file \"%s\": %m",
- statfile)));
- pgstat_reset_after_failure();
- return;
+ char *tmpfile;
+ char *statfile;
+
+ if (!pgstat_get_filenames_for_kind(PGSTAT_KIND_CUSTOM_MIN + idx - 1,
+ &tmpfile, &statfile))
+ continue;
+
+ elog(DEBUG2, "reading stats file \"%s\"", statfile);
+
+ /*
+ * Try to open the stats file. If it doesn't exist, the backends
+ * simply returns zero for anything and statistics simply starts from
+ * scratch with empty counters.
+ *
+ * ENOENT is a possibility if stats collection was previously disabled
+ * or has not yet written the stats file for the first time. Any
+ * other failure condition is suspicious.
+ */
+ if ((fpin[idx] = AllocateFile(statfile, PG_BINARY_R)) == NULL)
+ {
+ pgstat_close_files(fpin, PGSTAT_MAX_FILES);
+ if (errno != ENOENT)
+ ereport(LOG,
+ (errcode_for_file_access(),
+ errmsg("could not open statistics file \"%s\": %m",
+ statfile)));
+ pgstat_reset_after_failure();
+ pfree(tmpfile);
+ pfree(statfile);
+ return;
+ }
+
+ pfree(tmpfile);
+ pfree(statfile);
}
/*
* Verify it's of the expected format.
*/
- if (!read_chunk_s(fpin, &format_id))
+ if (!pgstat_read_chunk_s(fpin[PGSTAT_BUILTIN_FILE], &format_id))
{
elog(WARNING, "could not read format ID");
goto error;
@@ -1809,7 +1978,7 @@ pgstat_read_statsfile(void)
*/
for (;;)
{
- int t = fgetc(fpin);
+ int t = fgetc(fpin[PGSTAT_BUILTIN_FILE]);
switch (t)
{
@@ -1820,7 +1989,7 @@ pgstat_read_statsfile(void)
char *ptr;
/* entry for fixed-numbered stats */
- if (!read_chunk_s(fpin, &kind))
+ if (!pgstat_read_chunk_s(fpin[PGSTAT_BUILTIN_FILE], &kind))
{
elog(WARNING, "could not read stats kind for entry of type %c", t);
goto error;
@@ -1860,7 +2029,7 @@ pgstat_read_statsfile(void)
info->shared_data_off;
}
- if (!read_chunk(fpin, ptr, info->shared_data_len))
+ if (!pgstat_read_chunk(fpin[PGSTAT_BUILTIN_FILE], ptr, info->shared_data_len))
{
elog(WARNING, "could not read data of stats kind %u for entry of type %c with size %u",
kind, t, info->shared_data_len);
@@ -1875,13 +2044,14 @@ pgstat_read_statsfile(void)
PgStat_HashKey key;
PgStatShared_HashEntry *p;
PgStatShared_Common *header;
+ const PgStat_KindInfo *kind_info = NULL;
CHECK_FOR_INTERRUPTS();
if (t == PGSTAT_FILE_ENTRY_HASH)
{
/* normal stats entry, identified by PgStat_HashKey */
- if (!read_chunk_s(fpin, &key))
+ if (!pgstat_read_chunk_s(fpin[PGSTAT_BUILTIN_FILE], &key))
{
elog(WARNING, "could not read key for entry of type %c", t);
goto error;
@@ -1895,7 +2065,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,
@@ -1906,16 +2077,15 @@ 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;
- if (!read_chunk_s(fpin, &kind))
+ if (!pgstat_read_chunk_s(fpin[PGSTAT_BUILTIN_FILE], &kind))
{
elog(WARNING, "could not read stats kind for entry of type %c", t);
goto error;
}
- if (!read_chunk_s(fpin, &name))
+ if (!pgstat_read_chunk_s(fpin[PGSTAT_BUILTIN_FILE], &name))
{
elog(WARNING, "could not read name of stats kind %u for entry of type %c",
kind, t);
@@ -1946,7 +2116,7 @@ pgstat_read_statsfile(void)
if (!kind_info->from_serialized_name(&name, &key))
{
/* skip over data for entry we don't care about */
- if (fseek(fpin, pgstat_get_entry_len(kind), SEEK_CUR) != 0)
+ if (fseek(fpin[PGSTAT_BUILTIN_FILE], pgstat_get_entry_len(kind), SEEK_CUR) != 0)
{
elog(WARNING, "could not seek \"%s\" of stats kind %u for entry of type %c",
NameStr(name), kind, t);
@@ -1990,9 +2160,9 @@ pgstat_read_statsfile(void)
key.objid, t);
}
- if (!read_chunk(fpin,
- pgstat_get_entry_data(key.kind, header),
- pgstat_get_entry_len(key.kind)))
+ if (!pgstat_read_chunk(fpin[PGSTAT_BUILTIN_FILE],
+ pgstat_get_entry_data(key.kind, header),
+ pgstat_get_entry_len(key.kind)))
{
elog(WARNING, "could not read data for entry %u/%u/%" PRIu64 " of type %c",
key.kind, key.dboid,
@@ -2000,6 +2170,28 @@ pgstat_read_statsfile(void)
goto error;
}
+ /*
+ * A plug-in is reading extra data. If the plug-in fails
+ * to read the file, close the file, report the error, and
+ * move on.
+ */
+ if (kind_info->from_serialized_extra)
+ {
+ FILE *fp = fpin[(key.kind - PGSTAT_KIND_CUSTOM_MIN) + 1];
+
+ Assert(fp);
+
+ if (!kind_info->from_serialized_extra(&key, header, fp))
+ {
+ char *statfile = CUSTOM_STATFILE_PATH(key.kind);
+
+ pgstat_report_statfile_error(key.kind, statfile);
+
+ fpin[(key.kind - PGSTAT_KIND_CUSTOM_MIN) + 1] = NULL;
+ pfree(statfile);
+ }
+ }
+
break;
}
case PGSTAT_FILE_ENTRY_END:
@@ -2008,7 +2200,7 @@ pgstat_read_statsfile(void)
* check that PGSTAT_FILE_ENTRY_END actually signals end of
* file
*/
- if (fgetc(fpin) != EOF)
+ if (fgetc(fpin[PGSTAT_BUILTIN_FILE]) != EOF)
{
elog(WARNING, "could not read end-of-file");
goto error;
@@ -2023,18 +2215,25 @@ pgstat_read_statsfile(void)
}
done:
- FreeFile(fpin);
+ for (int idx = 0; idx <= PGSTAT_MAX_CUSTOM_FILES; idx++)
+ {
+ char *tmpfile;
+ char *statfile;
- elog(DEBUG2, "removing permanent stats file \"%s\"", statfile);
- unlink(statfile);
+ if (!pgstat_get_filenames_for_kind(PGSTAT_KIND_CUSTOM_MIN + idx - 1,
+ &tmpfile, &statfile))
+ continue;
+
+ pgstat_close_statfile(fpin[idx], statfile);
+
+ pfree(tmpfile);
+ pfree(statfile);
+ }
return;
error:
- ereport(LOG,
- (errmsg("corrupted statistics file \"%s\"", statfile)));
-
- pgstat_reset_after_failure();
+ pgstat_report_statfile_error(PGSTAT_BUILTIN_FILE, statfile);
goto done;
}
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 4d2b8aa6081..c317bf883a0 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -303,6 +303,15 @@ typedef struct PgStat_KindInfo
const PgStatShared_Common *header, NameData *name);
bool (*from_serialized_name) (const NameData *name, PgStat_HashKey *key);
+ /*
+ * For custom, variable-numbered stats, serialize/de-serialize extra data
+ * per entry. Optional.
+ */
+ bool (*from_serialized_extra) (PgStat_HashKey *key,
+ const PgStatShared_Common *header, FILE *fd);
+ void (*to_serialized_extra) (const PgStat_HashKey *key,
+ const PgStatShared_Common *header, FILE *fd);
+
/*
* For fixed-numbered statistics: Initialize shared memory state.
*
@@ -984,4 +993,13 @@ pgstat_get_custom_snapshot_data(PgStat_Kind kind)
return pgStatLocal.snapshot.custom_data[idx];
}
+/*
+ * Helpers for reading/writing stats files.
+ */
+extern void pgstat_write_chunk(FILE *fpout, void *ptr, size_t len);
+extern bool pgstat_read_chunk(FILE *fpin, void *ptr, size_t len);
+
+#define pgstat_write_chunk_s(fpout, ptr) pgstat_write_chunk(fpout, ptr, sizeof(*ptr))
+#define pgstat_read_chunk_s(fpin, ptr) pgstat_read_chunk(fpin, ptr, sizeof(*ptr))
+
#endif /* PGSTAT_INTERNAL_H */
--
2.43.0