PostgreSQL Source Code git master
Loading...
Searching...
No Matches
test_custom_var_stats.c
Go to the documentation of this file.
1/*------------------------------------------------------------------------------------
2 *
3 * test_custom_var_stats.c
4 * Test module for variable-sized custom pgstats
5 *
6 * Copyright (c) 2025-2026, PostgreSQL Global Development Group
7 *
8 * IDENTIFICATION
9 * src/test/modules/test_custom_var_stats/test_custom_var_stats.c
10 *
11 * ------------------------------------------------------------------------------------
12 */
13#include "postgres.h"
14
15#include "access/htup_details.h"
16#include "common/hashfn.h"
17#include "funcapi.h"
19#include "storage/fd.h"
20#include "utils/builtins.h"
22
24 .name = "test_custom_var_stats",
25 .version = PG_VERSION
26);
27
28/* Local helpers for stats file I/O */
29#define write_chunk(fpout, ptr, len) (fwrite(ptr, len, 1, fpout) == 1)
30#define write_chunk_s(fpout, ptr) write_chunk(fpout, ptr, sizeof(*ptr))
31#define read_chunk(fpin, ptr, len) (fread(ptr, 1, len, fpin) == (len))
32#define read_chunk_s(fpin, ptr) read_chunk(fpin, ptr, sizeof(*ptr))
33
34#define TEST_CUSTOM_VAR_MAGIC_NUMBER (0xBEEFBEEF)
35
36/*--------------------------------------------------------------------------
37 * Macros and constants
38 *--------------------------------------------------------------------------
39 */
40
41/*
42 * Kind ID for test_custom_var_stats statistics.
43 */
44#define PGSTAT_KIND_TEST_CUSTOM_VAR_STATS 25
45
46/* File paths for auxiliary data serialization */
47#define TEST_CUSTOM_AUX_DATA_DESC "pg_stat/test_custom_var_stats_desc.stats"
48
49/*
50 * Hash statistic name to generate entry index for pgstat lookup.
51 */
52#define PGSTAT_CUSTOM_VAR_STATS_IDX(name) hash_bytes_extended((const unsigned char *) name, strlen(name), 0)
53
54/*--------------------------------------------------------------------------
55 * Type definitions
56 *--------------------------------------------------------------------------
57 */
58
59/* Backend-local pending statistics before flush to shared memory */
61{
62 PgStat_Counter numcalls; /* times statistic was incremented */
64
65/* Shared memory statistics entry visible to all backends */
67{
68 PgStatShared_Common header; /* standard pgstat entry header */
69 PgStat_StatCustomVarEntry stats; /* custom statistics data */
70 dsa_pointer description; /* pointer to description string in DSA */
72
73/*--------------------------------------------------------------------------
74 * Global Variables
75 *--------------------------------------------------------------------------
76 */
77
78/* File handle for auxiliary data serialization */
80
81/* Current write offset in fd_description file */
83
84/* DSA area for storing variable-length description strings */
86
87/*--------------------------------------------------------------------------
88 * Function prototypes
89 *--------------------------------------------------------------------------
90 */
91
92/* Flush callback: merge pending stats into shared memory */
94 bool nowait);
95
96/* Serialization callback: write auxiliary entry data */
98 const PgStatShared_Common *header,
99 FILE *statfile);
100
101/* Deserialization callback: read auxiliary entry data */
103 PgStatShared_Common *header,
104 FILE *statfile);
105
106/* Finish callback: end of statistics file operations */
108
109/*--------------------------------------------------------------------------
110 * Custom kind configuration
111 *--------------------------------------------------------------------------
112 */
113
115 .name = "test_custom_var_stats",
116 .fixed_amount = false, /* variable number of entries */
117 .write_to_file = true, /* persist across restarts */
118 .track_entry_count = true, /* count active entries */
119 .accessed_across_databases = true, /* global statistics */
120 .shared_size = sizeof(PgStatShared_CustomVarEntry),
121 .shared_data_off = offsetof(PgStatShared_CustomVarEntry, stats),
122 .shared_data_len = sizeof(((PgStatShared_CustomVarEntry *) 0)->stats),
123 .pending_size = sizeof(PgStat_StatCustomVarEntry),
124 .flush_pending_cb = test_custom_stats_var_flush_pending_cb,
125 .to_serialized_data = test_custom_stats_var_to_serialized_data,
126 .from_serialized_data = test_custom_stats_var_from_serialized_data,
128};
129
130/*--------------------------------------------------------------------------
131 * Module initialization
132 *--------------------------------------------------------------------------
133 */
134
135void
137{
138 /* Register custom statistics kind */
140}
141
142/*--------------------------------------------------------------------------
143 * Statistics callback functions
144 *--------------------------------------------------------------------------
145 */
146
147/*
148 * test_custom_stats_var_flush_pending_cb
149 * Merge pending backend statistics into shared memory
150 *
151 * Called by pgstat collector to flush accumulated local statistics
152 * to shared memory where other backends can read them.
153 *
154 * Returns false only if nowait=true and lock acquisition fails.
155 */
156static bool
158{
160 PgStatShared_CustomVarEntry *shared_entry;
161
163 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
164
165 if (!pgstat_lock_entry(entry_ref, nowait))
166 return false;
167
168 /* Add pending counts to shared totals */
169 shared_entry->stats.numcalls += pending_entry->numcalls;
170
171 pgstat_unlock_entry(entry_ref);
172
173 return true;
174}
175
176/*
177 * test_custom_stats_var_to_serialized_data() -
178 *
179 * Serialize auxiliary data (descriptions) for custom statistics entries
180 * to a secondary statistics file. This is called while writing the statistics
181 * to disk.
182 *
183 * This callback writes a mix of data within the main pgstats file and a
184 * secondary statistics file. The following data is written to the main file for
185 * each entry:
186 * - An arbitrary magic number.
187 * - An offset. This is used to know the location we need to look at
188 * to retrieve the information from the second file.
189 *
190 * The following data is written to the secondary statistics file:
191 * - The entry key, cross-checked with the data from the main file
192 * when reloaded.
193 * - The length of the description.
194 * - The description data itself.
195 */
196static bool
198 const PgStatShared_Common *header,
199 FILE *statfile)
200{
201 char *description;
202 size_t len;
203 const PgStatShared_CustomVarEntry *entry = (const PgStatShared_CustomVarEntry *) header;
204 bool found;
206
207 /*
208 * First mark the main file with a magic number, keeping a trace that some
209 * auxiliary data will exist in the secondary statistics file.
210 */
212 return false;
213
214 /* Open statistics file for writing. */
215 if (!fd_description)
216 {
218 if (fd_description == NULL)
219 {
220 ereport(LOG,
222 errmsg("could not open statistics file \"%s\" for writing: %m",
224 return false;
225 }
226
227 /* Initialize offset for secondary statistics file. */
229 }
230
231 /* Write offset to the main data file */
233 return false;
234
235 /*
236 * First write the entry key to the secondary statistics file. This will
237 * be cross-checked with the key read from main stats file at loading
238 * time.
239 */
241 return false;
243
245 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
246
247 /* Handle entries without descriptions */
249 {
250 /* length to description file */
251 len = 0;
253 return false;
254 fd_description_offset += sizeof(size_t);
255 return true;
256 }
257
258 /*
259 * Retrieve description from DSA, then write the length followed by the
260 * description.
261 */
263 entry->description);
264 len = strlen(description) + 1;
266 return false;
268 return false;
269
270 /*
271 * Update offset for next entry, counting for the length (size_t) of the
272 * description and the description contents.
273 */
274 fd_description_offset += len + sizeof(size_t);
275 return true;
276}
277
278/*
279 * test_custom_stats_var_from_serialized_data() -
280 *
281 * Read auxiliary data (descriptions) for custom statistics entries from
282 * the secondary statistics file. This is called while loading the statistics
283 * at startup.
284 *
285 * See the top of test_custom_stats_var_to_serialized_data() for a
286 * detailed description of the data layout read here.
287 */
288static bool
290 PgStatShared_Common *header,
291 FILE *statfile)
292{
295 size_t len;
296 pgoff_t offset;
297 char *buffer;
298 bool found;
301
302 /* Check the magic number first, in the main file. */
304 {
305 elog(WARNING, "failed to read magic number from statistics file");
306 return false;
307 }
308
310 {
311 elog(WARNING, "found magic number %u from statistics file, should be %u",
313 return false;
314 }
315
316 /*
317 * Read the offset from the main stats file, to be able to read the
318 * auxiliary data from the secondary statistics file.
319 */
320 if (!read_chunk_s(statfile, &offset))
321 {
322 elog(WARNING, "failed to read metadata offset from statistics file");
323 return false;
324 }
325
326 /* Open statistics file for reading if not already open */
327 if (!fd_description)
328 {
330 if (fd_description == NULL)
331 {
332 if (errno != ENOENT)
333 ereport(LOG,
335 errmsg("could not open statistics file \"%s\" for reading: %m",
338 return false;
339 }
340 }
341
342 /* Read data from the secondary statistics file, at the specified offset */
343 if (fseeko(fd_description, offset, SEEK_SET) != 0)
344 {
345 elog(WARNING, "could not seek in file \"%s\": %m",
347 return false;
348 }
349
350 /* Read the hash key from the secondary statistics file */
352 {
353 elog(WARNING, "failed to read hash key from file");
354 return false;
355 }
356
357 /* Check key consistency */
358 if (file_key.kind != key->kind ||
359 file_key.dboid != key->dboid ||
360 file_key.objid != key->objid)
361 {
362 elog(WARNING, "found entry key %u/%u/%" PRIu64 " not matching with %u/%u/%" PRIu64,
363 file_key.kind, file_key.dboid, file_key.objid,
364 key->kind, key->dboid, key->objid);
365 return false;
366 }
367
368 entry = (PgStatShared_CustomVarEntry *) header;
369
370 /* Read the description length and its data */
372 {
373 elog(WARNING, "failed to read metadata length from statistics file");
374 return false;
375 }
376
377 /* Handle empty descriptions */
378 if (len == 0)
379 {
381 return true;
382 }
383
384 /* Initialize DSA if needed */
386 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
387
389 {
390 elog(WARNING, "could not access DSA for custom statistics descriptions");
391 return false;
392 }
393
394 buffer = palloc(len);
395 if (!read_chunk(fd_description, buffer, len))
396 {
397 pfree(buffer);
398 elog(WARNING, "failed to read description from file");
399 return false;
400 }
401
402 /* Allocate space in DSA and copy the description */
405 entry->description = dp;
406 pfree(buffer);
407
408 return true;
409}
410
411/*
412 * test_custom_stats_var_finish() -
413 *
414 * Cleanup function called at the end of statistics file operations.
415 * Handles closing files and cleanup based on the operation type.
416 */
417static void
419{
420 switch (status)
421 {
422 case STATS_WRITE:
423 if (!fd_description)
424 return;
425
427
428 /* Check for write errors and cleanup if necessary */
430 {
431 ereport(LOG,
433 errmsg("could not write to file \"%s\": %m",
437 }
438 else if (FreeFile(fd_description) < 0)
439 {
440 ereport(LOG,
442 errmsg("could not close file \"%s\": %m",
445 }
446 break;
447
448 case STATS_READ:
449 if (fd_description)
451
452 /* Remove the file after reading */
453 elog(DEBUG2, "removing file \"%s\"", TEST_CUSTOM_AUX_DATA_DESC);
455 break;
456
457 case STATS_DISCARD:
458 {
459 int ret;
460
461 /* Attempt to remove the file */
463 if (ret != 0)
464 {
465 if (errno == ENOENT)
466 elog(LOG,
467 "didn't need to unlink file \"%s\" - didn't exist",
469 else
470 ereport(LOG,
472 errmsg("could not unlink file \"%s\": %m",
474 }
475 else
476 {
477 ereport(LOG,
478 (errmsg_internal("unlinked file \"%s\"",
480 }
481 }
482 break;
483 }
484
486}
487
488/*--------------------------------------------------------------------------
489 * Helper functions
490 *--------------------------------------------------------------------------
491 */
492
493/*
494 * test_custom_stats_var_fetch_entry
495 * Look up custom statistic by name
496 *
497 * Returns statistics entry from shared memory, or NULL if not found.
498 */
509
510/*--------------------------------------------------------------------------
511 * SQL-callable functions
512 *--------------------------------------------------------------------------
513 */
514
515/*
516 * test_custom_stats_var_create
517 * Create new custom statistic entry
518 *
519 * Initializes a statistics entry with the given name and description.
520 */
522Datum
524{
525 PgStat_EntryRef *entry_ref;
526 PgStatShared_CustomVarEntry *shared_entry;
530 bool found;
531
532 /* Validate name length first */
536 errmsg("custom statistic name \"%s\" is too long", stat_name),
537 errdetail("Name must be less than %d characters.", NAMEDATALEN)));
538
539 /* Initialize DSA and description provided */
541 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
542
545 (errmsg("could not access DSA for custom statistics descriptions")));
546
547 /* Allocate space in DSA and copy description */
551 strlen(description) + 1);
552
553 /* Create or get existing entry */
556
557 if (!entry_ref)
559
560 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
561
562 /* Zero-initialize statistics */
563 memset(&shared_entry->stats, 0, sizeof(shared_entry->stats));
564
565 /* Store description pointer */
566 shared_entry->description = dp;
567
568 pgstat_unlock_entry(entry_ref);
569
571}
572
573/*
574 * test_custom_stats_var_update
575 * Increment custom statistic counter
576 *
577 * Increments call count in backend-local memory. Changes are flushed
578 * to shared memory by the statistics collector.
579 */
581Datum
597
598/*
599 * test_custom_stats_var_drop
600 * Remove custom statistic entry
601 *
602 * Drops the named statistic from shared memory.
603 */
605Datum
607{
609
610 /* Drop entry and request GC if the entry could not be freed */
614
616}
617
618/*
619 * test_custom_stats_var_report
620 * Retrieve custom statistic values
621 *
622 * Returns single row with statistic name, call count, and description if the
623 * statistic exists, otherwise returns no rows.
624 */
626Datum
628{
630 char *stat_name;
632
633 if (SRF_IS_FIRSTCALL())
634 {
635 TupleDesc tupdesc;
636 MemoryContext oldcontext;
637
638 /* Initialize SRF context */
640 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
641
642 /* Get composite return type */
643 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
644 elog(ERROR, "test_custom_stats_var_report: return type is not composite");
645
646 funcctx->tuple_desc = BlessTupleDesc(tupdesc);
647 funcctx->max_calls = 1; /* single row result */
648
649 MemoryContextSwitchTo(oldcontext);
650 }
651
653
654 if (funcctx->call_cntr < funcctx->max_calls)
655 {
656 Datum values[3];
657 bool nulls[3] = {false, false, false};
658 HeapTuple tuple;
659 PgStat_EntryRef *entry_ref;
660 PgStatShared_CustomVarEntry *shared_entry;
661 char *description = NULL;
662 bool found;
663
666
667 /* Return row only if entry exists */
668 if (stat_entry)
669 {
670 /* Get entry ref to access shared entry */
673
674 if (entry_ref)
675 {
676 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
677
678 /* Get description from DSA if available */
679 if (DsaPointerIsValid(shared_entry->description))
680 {
682 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
683
686 }
687 }
688
690 values[1] = Int64GetDatum(stat_entry->numcalls);
691
692 if (description)
694 else
695 nulls[2] = true;
696
697 tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
699 }
700 }
701
703}
static Datum values[MAXATTR]
Definition bootstrap.c:190
#define PG_BINARY_R
Definition c.h:1433
uint32_t uint32
Definition c.h:683
#define PG_BINARY_W
Definition c.h:1434
memcpy(sums, checksumBaseOffsets, sizeof(checksumBaseOffsets))
void * dsa_get_address(dsa_area *area, dsa_pointer dp)
Definition dsa.c:954
uint64 dsa_pointer
Definition dsa.h:62
#define dsa_allocate(area, size)
Definition dsa.h:109
#define InvalidDsaPointer
Definition dsa.h:78
#define DsaPointerIsValid(x)
Definition dsa.h:106
dsa_area * GetNamedDSA(const char *name, bool *found)
int errcode_for_file_access(void)
Definition elog.c:898
int errcode(int sqlerrcode)
Definition elog.c:875
#define LOG
Definition elog.h:32
int errdetail(const char *fmt,...) pg_attribute_printf(1
int int errmsg_internal(const char *fmt,...) pg_attribute_printf(1
#define WARNING
Definition elog.h:37
#define DEBUG2
Definition elog.h:30
#define ERROR
Definition elog.h:40
#define elog(elevel,...)
Definition elog.h:228
#define ereport(elevel,...)
Definition elog.h:152
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
int FreeFile(FILE *file)
Definition fd.c:2827
FILE * AllocateFile(const char *name, const char *mode)
Definition fd.c:2628
#define PG_RETURN_VOID()
Definition fmgr.h:350
#define PG_GETARG_TEXT_PP(n)
Definition fmgr.h:310
#define PG_MODULE_MAGIC_EXT(...)
Definition fmgr.h:540
#define PG_FUNCTION_INFO_V1(funcname)
Definition fmgr.h:417
#define PG_FUNCTION_ARGS
Definition fmgr.h:193
TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo, Oid *resultTypeId, TupleDesc *resultTupleDesc)
Definition funcapi.c:276
#define SRF_IS_FIRSTCALL()
Definition funcapi.h:304
#define SRF_PERCALL_SETUP()
Definition funcapi.h:308
@ TYPEFUNC_COMPOSITE
Definition funcapi.h:149
#define SRF_RETURN_NEXT(_funcctx, _result)
Definition funcapi.h:310
#define SRF_FIRSTCALL_INIT()
Definition funcapi.h:306
static Datum HeapTupleGetDatum(const HeapTupleData *tuple)
Definition funcapi.h:230
#define SRF_RETURN_DONE(_funcctx)
Definition funcapi.h:328
HeapTuple heap_form_tuple(TupleDesc tupleDescriptor, const Datum *values, const bool *isnull)
Definition heaptuple.c:1025
void pfree(void *pointer)
Definition mcxt.c:1619
void * palloc(Size size)
Definition mcxt.c:1390
static char * errmsg
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition palloc.h:138
#define NAMEDATALEN
const void size_t len
PgStat_EntryRef * pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
Definition pgstat.c:1309
void * pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
Definition pgstat.c:962
void pgstat_reset_of_kind(PgStat_Kind kind)
Definition pgstat.c:904
void pgstat_register_kind(PgStat_Kind kind, const PgStat_KindInfo *kind_info)
Definition pgstat.c:1507
int64 PgStat_Counter
Definition pgstat.h:71
PgStat_StatsFileOp
@ STATS_WRITE
@ STATS_READ
@ STATS_DISCARD
void pgstat_request_entry_refs_gc(void)
PgStat_EntryRef * pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create, bool *created_entry)
bool pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool missing_ok)
void pgstat_unlock_entry(PgStat_EntryRef *entry_ref)
bool pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait)
PgStat_EntryRef * pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, uint64 objid, bool nowait)
off_t pgoff_t
Definition port.h:422
static Datum Int64GetDatum(int64 X)
Definition postgres.h:426
uint64_t Datum
Definition postgres.h:70
#define PointerGetDatum(X)
Definition postgres.h:354
#define InvalidOid
static int fb(int x)
PgStat_StatCustomVarEntry stats
PgStatShared_Common * shared_stats
const char *const name
static dsa_area * custom_stats_description_dsa
static void test_custom_stats_var_finish(PgStat_StatsFileOp status)
static pgoff_t fd_description_offset
void _PG_init(void)
Datum test_custom_stats_var_update(PG_FUNCTION_ARGS)
#define read_chunk(fpin, ptr, len)
Datum test_custom_stats_var_drop(PG_FUNCTION_ARGS)
static const PgStat_KindInfo custom_stats
#define write_chunk(fpout, ptr, len)
Datum test_custom_stats_var_create(PG_FUNCTION_ARGS)
static bool test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key, const PgStatShared_Common *header, FILE *statfile)
Datum test_custom_stats_var_report(PG_FUNCTION_ARGS)
static bool test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait)
#define TEST_CUSTOM_AUX_DATA_DESC
static PgStat_StatCustomVarEntry * test_custom_stats_var_fetch_entry(const char *stat_name)
#define write_chunk_s(fpout, ptr)
#define read_chunk_s(fpin, ptr)
static bool test_custom_stats_var_from_serialized_data(const PgStat_HashKey *key, PgStatShared_Common *header, FILE *statfile)
#define PGSTAT_KIND_TEST_CUSTOM_VAR_STATS
#define TEST_CUSTOM_VAR_MAGIC_NUMBER
#define PGSTAT_CUSTOM_VAR_STATS_IDX(name)
static FILE * fd_description
text * cstring_to_text(const char *s)
Definition varlena.c:184
char * text_to_cstring(const text *t)
Definition varlena.c:217
const char * description
const char * name
#define fseeko(stream, offset, origin)
Definition win32_port.h:206