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#define TEST_CUSTOM_VAR_MAGIC_NUMBER (0xBEEFBEEF)
29
30/*--------------------------------------------------------------------------
31 * Macros and constants
32 *--------------------------------------------------------------------------
33 */
34
35/*
36 * Kind ID for test_custom_var_stats statistics.
37 */
38#define PGSTAT_KIND_TEST_CUSTOM_VAR_STATS 25
39
40/* File paths for auxiliary data serialization */
41#define TEST_CUSTOM_AUX_DATA_DESC "pg_stat/test_custom_var_stats_desc.stats"
42
43/*
44 * Hash statistic name to generate entry index for pgstat lookup.
45 */
46#define PGSTAT_CUSTOM_VAR_STATS_IDX(name) hash_bytes_extended((const unsigned char *) name, strlen(name), 0)
47
48/*--------------------------------------------------------------------------
49 * Type definitions
50 *--------------------------------------------------------------------------
51 */
52
53/* Backend-local pending statistics before flush to shared memory */
55{
56 PgStat_Counter numcalls; /* times statistic was incremented */
58
59/* Shared memory statistics entry visible to all backends */
61{
62 PgStatShared_Common header; /* standard pgstat entry header */
63 PgStat_StatCustomVarEntry stats; /* custom statistics data */
64 dsa_pointer description; /* pointer to description string in DSA */
66
67/*--------------------------------------------------------------------------
68 * Global Variables
69 *--------------------------------------------------------------------------
70 */
71
72/* File handle for auxiliary data serialization */
74
75/* Current write offset in fd_description file */
77
78/* DSA area for storing variable-length description strings */
80
81/*--------------------------------------------------------------------------
82 * Function prototypes
83 *--------------------------------------------------------------------------
84 */
85
86/* Flush callback: merge pending stats into shared memory */
88 bool nowait);
89
90/* Serialization callback: write auxiliary entry data */
92 const PgStatShared_Common *header,
93 FILE *statfile);
94
95/* Deserialization callback: read auxiliary entry data */
97 PgStatShared_Common *header,
98 FILE *statfile);
99
100/* Finish callback: end of statistics file operations */
102
103/*--------------------------------------------------------------------------
104 * Custom kind configuration
105 *--------------------------------------------------------------------------
106 */
107
109 .name = "test_custom_var_stats",
110 .fixed_amount = false, /* variable number of entries */
111 .write_to_file = true, /* persist across restarts */
112 .track_entry_count = true, /* count active entries */
113 .accessed_across_databases = true, /* global statistics */
114 .shared_size = sizeof(PgStatShared_CustomVarEntry),
115 .shared_data_off = offsetof(PgStatShared_CustomVarEntry, stats),
116 .shared_data_len = sizeof(((PgStatShared_CustomVarEntry *) 0)->stats),
117 .pending_size = sizeof(PgStat_StatCustomVarEntry),
118 .flush_pending_cb = test_custom_stats_var_flush_pending_cb,
119 .to_serialized_data = test_custom_stats_var_to_serialized_data,
120 .from_serialized_data = test_custom_stats_var_from_serialized_data,
122};
123
124/*--------------------------------------------------------------------------
125 * Module initialization
126 *--------------------------------------------------------------------------
127 */
128
129void
131{
132 /* Must be loaded via shared_preload_libraries */
134 return;
135
136 /* Register custom statistics kind */
138}
139
140/*--------------------------------------------------------------------------
141 * Statistics callback functions
142 *--------------------------------------------------------------------------
143 */
144
145/*
146 * test_custom_stats_var_flush_pending_cb
147 * Merge pending backend statistics into shared memory
148 *
149 * Called by pgstat collector to flush accumulated local statistics
150 * to shared memory where other backends can read them.
151 *
152 * Returns false only if nowait=true and lock acquisition fails.
153 */
154static bool
156{
158 PgStatShared_CustomVarEntry *shared_entry;
159
161 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
162
163 if (!pgstat_lock_entry(entry_ref, nowait))
164 return false;
165
166 /* Add pending counts to shared totals */
167 shared_entry->stats.numcalls += pending_entry->numcalls;
168
169 pgstat_unlock_entry(entry_ref);
170
171 return true;
172}
173
174/*
175 * test_custom_stats_var_to_serialized_data() -
176 *
177 * Serialize auxiliary data (descriptions) for custom statistics entries
178 * to a secondary statistics file. This is called while writing the statistics
179 * to disk.
180 *
181 * This callback writes a mix of data within the main pgstats file and a
182 * secondary statistics file. The following data is written to the main file for
183 * each entry:
184 * - An arbitrary magic number.
185 * - An offset. This is used to know the location we need to look at
186 * to retrieve the information from the second file.
187 *
188 * The following data is written to the secondary statistics file:
189 * - The entry key, cross-checked with the data from the main file
190 * when reloaded.
191 * - The length of the description.
192 * - The description data itself.
193 */
194static void
196 const PgStatShared_Common *header,
197 FILE *statfile)
198{
199 char *description;
200 size_t len;
201 const PgStatShared_CustomVarEntry *entry = (const PgStatShared_CustomVarEntry *) header;
202 bool found;
204
205 /*
206 * First mark the main file with a magic number, keeping a trace that some
207 * auxiliary data will exist in the secondary statistics file.
208 */
210
211 /* Open statistics file for writing. */
212 if (!fd_description)
213 {
215 if (fd_description == NULL)
216 {
217 ereport(LOG,
219 errmsg("could not open statistics file \"%s\" for writing: %m",
221 return;
222 }
223
224 /* Initialize offset for secondary statistics file. */
226 }
227
228 /* Write offset to the main data file */
230
231 /*
232 * First write the entry key to the secondary statistics file. This will
233 * be cross-checked with the key read from main stats file at loading
234 * time.
235 */
238
240 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
241
242 /* Handle entries without descriptions */
244 {
245 /* length to description file */
246 len = 0;
248 fd_description_offset += sizeof(size_t);
249 return;
250 }
251
252 /*
253 * Retrieve description from DSA, then write the length followed by the
254 * description.
255 */
257 entry->description);
258 len = strlen(description) + 1;
261
262 /*
263 * Update offset for next entry, counting for the length (size_t) of the
264 * description and the description contents.
265 */
266 fd_description_offset += len + sizeof(size_t);
267}
268
269/*
270 * test_custom_stats_var_from_serialized_data() -
271 *
272 * Read auxiliary data (descriptions) for custom statistics entries from
273 * the secondary statistics file. This is called while loading the statistics
274 * at startup.
275 *
276 * See the top of test_custom_stats_var_to_serialized_data() for a
277 * detailed description of the data layout read here.
278 */
279static bool
281 PgStatShared_Common *header,
282 FILE *statfile)
283{
286 size_t len;
287 pgoff_t offset;
288 char *buffer;
289 bool found;
292
293 /* Check the magic number first, in the main file. */
295 {
296 elog(WARNING, "failed to read magic number from statistics file");
297 return false;
298 }
299
301 {
302 elog(WARNING, "found magic number %u from statistics file, should be %u",
304 return false;
305 }
306
307 /*
308 * Read the offset from the main stats file, to be able to read the
309 * auxiliary data from the secondary statistics file.
310 */
311 if (!pgstat_read_chunk_s(statfile, &offset))
312 {
313 elog(WARNING, "failed to read metadata offset from statistics file");
314 return false;
315 }
316
317 /* Open statistics file for reading if not already open */
318 if (!fd_description)
319 {
321 if (fd_description == NULL)
322 {
323 if (errno != ENOENT)
324 ereport(LOG,
326 errmsg("could not open statistics file \"%s\" for reading: %m",
329 return false;
330 }
331 }
332
333 /* Read data from the secondary statistics file, at the specified offset */
334 if (fseeko(fd_description, offset, SEEK_SET) != 0)
335 {
336 elog(WARNING, "could not seek in file \"%s\": %m",
338 return false;
339 }
340
341 /* Read the hash key from the secondary statistics file */
343 {
344 elog(WARNING, "failed to read hash key from file");
345 return false;
346 }
347
348 /* Check key consistency */
349 if (file_key.kind != key->kind ||
350 file_key.dboid != key->dboid ||
351 file_key.objid != key->objid)
352 {
353 elog(WARNING, "found entry key %u/%u/%" PRIu64 " not matching with %u/%u/%" PRIu64,
354 file_key.kind, file_key.dboid, file_key.objid,
355 key->kind, key->dboid, key->objid);
356 return false;
357 }
358
359 entry = (PgStatShared_CustomVarEntry *) header;
360
361 /* Read the description length and its data */
363 {
364 elog(WARNING, "failed to read metadata length from statistics file");
365 return false;
366 }
367
368 /* Handle empty descriptions */
369 if (len == 0)
370 {
372 return true;
373 }
374
375 /* Initialize DSA if needed */
377 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
378
380 {
381 elog(WARNING, "could not access DSA for custom statistics descriptions");
382 return false;
383 }
384
385 buffer = palloc(len);
386 if (!pgstat_read_chunk(fd_description, buffer, len))
387 {
388 pfree(buffer);
389 elog(WARNING, "failed to read description from file");
390 return false;
391 }
392
393 /* Allocate space in DSA and copy the description */
396 entry->description = dp;
397 pfree(buffer);
398
399 return true;
400}
401
402/*
403 * test_custom_stats_var_finish() -
404 *
405 * Cleanup function called at the end of statistics file operations.
406 * Handles closing files and cleanup based on the operation type.
407 */
408static void
410{
411 switch (status)
412 {
413 case STATS_WRITE:
414 if (!fd_description)
415 return;
416
418
419 /* Check for write errors and cleanup if necessary */
421 {
422 ereport(LOG,
424 errmsg("could not write to file \"%s\": %m",
428 }
429 else if (FreeFile(fd_description) < 0)
430 {
431 ereport(LOG,
433 errmsg("could not close file \"%s\": %m",
436 }
437 break;
438
439 case STATS_READ:
440 if (fd_description)
442
443 /* Remove the file after reading */
444 elog(DEBUG2, "removing file \"%s\"", TEST_CUSTOM_AUX_DATA_DESC);
446 break;
447
448 case STATS_DISCARD:
449 {
450 int ret;
451
452 /* Attempt to remove the file */
454 if (ret != 0)
455 {
456 if (errno == ENOENT)
457 elog(LOG,
458 "didn't need to unlink file \"%s\" - didn't exist",
460 else
461 ereport(LOG,
463 errmsg("could not unlink file \"%s\": %m",
465 }
466 else
467 {
468 ereport(LOG,
469 (errmsg_internal("unlinked file \"%s\"",
471 }
472 }
473 break;
474 }
475
477}
478
479/*--------------------------------------------------------------------------
480 * Helper functions
481 *--------------------------------------------------------------------------
482 */
483
484/*
485 * test_custom_stats_var_fetch_entry
486 * Look up custom statistic by name
487 *
488 * Returns statistics entry from shared memory, or NULL if not found.
489 */
499
500/*--------------------------------------------------------------------------
501 * SQL-callable functions
502 *--------------------------------------------------------------------------
503 */
504
505/*
506 * test_custom_stats_var_create
507 * Create new custom statistic entry
508 *
509 * Initializes a statistics entry with the given name and description.
510 */
512Datum
514{
515 PgStat_EntryRef *entry_ref;
516 PgStatShared_CustomVarEntry *shared_entry;
520 bool found;
521
522 /* Validate name length first */
526 errmsg("custom statistic name \"%s\" is too long", stat_name),
527 errdetail("Name must be less than %d characters.", NAMEDATALEN)));
528
529 /* Initialize DSA and description provided */
531 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
532
535 (errmsg("could not access DSA for custom statistics descriptions")));
536
537 /* Allocate space in DSA and copy description */
541 strlen(description) + 1);
542
543 /* Create or get existing entry */
546
547 if (!entry_ref)
549
550 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
551
552 /* Zero-initialize statistics */
553 memset(&shared_entry->stats, 0, sizeof(shared_entry->stats));
554
555 /* Store description pointer */
556 shared_entry->description = dp;
557
558 pgstat_unlock_entry(entry_ref);
559
561}
562
563/*
564 * test_custom_stats_var_update
565 * Increment custom statistic counter
566 *
567 * Increments call count in backend-local memory. Changes are flushed
568 * to shared memory by the statistics collector.
569 */
571Datum
587
588/*
589 * test_custom_stats_var_drop
590 * Remove custom statistic entry
591 *
592 * Drops the named statistic from shared memory.
593 */
595Datum
607
608/*
609 * test_custom_stats_var_report
610 * Retrieve custom statistic values
611 *
612 * Returns single row with statistic name, call count, and description if the
613 * statistic exists, otherwise returns no rows.
614 */
616Datum
618{
620 char *stat_name;
622
623 if (SRF_IS_FIRSTCALL())
624 {
625 TupleDesc tupdesc;
626 MemoryContext oldcontext;
627
628 /* Initialize SRF context */
630 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
631
632 /* Get composite return type */
633 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
634 elog(ERROR, "test_custom_stats_var_report: return type is not composite");
635
636 funcctx->tuple_desc = BlessTupleDesc(tupdesc);
637 funcctx->max_calls = 1; /* single row result */
638
639 MemoryContextSwitchTo(oldcontext);
640 }
641
643
644 if (funcctx->call_cntr < funcctx->max_calls)
645 {
646 Datum values[3];
647 bool nulls[3] = {false, false, false};
648 HeapTuple tuple;
649 PgStat_EntryRef *entry_ref;
650 PgStatShared_CustomVarEntry *shared_entry;
651 char *description = NULL;
652 bool found;
653
656
657 /* Return row only if entry exists */
658 if (stat_entry)
659 {
660 /* Get entry ref to access shared entry */
663
664 if (entry_ref)
665 {
666 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
667
668 /* Get description from DSA if available */
669 if (DsaPointerIsValid(shared_entry->description))
670 {
672 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
673
676 }
677 }
678
680 values[1] = Int64GetDatum(stat_entry->numcalls);
681
682 if (description)
684 else
685 nulls[2] = true;
686
687 tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
689 }
690 }
691
693}
static Datum values[MAXATTR]
Definition bootstrap.c:188
#define PG_BINARY_R
Definition c.h:1378
uint32_t uint32
Definition c.h:618
#define PG_BINARY_W
Definition c.h:1379
void * dsa_get_address(dsa_area *area, dsa_pointer dp)
Definition dsa.c:957
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:897
int errcode(int sqlerrcode)
Definition elog.c:874
#define LOG
Definition elog.h:31
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:36
#define DEBUG2
Definition elog.h:29
#define ERROR
Definition elog.h:39
#define elog(elevel,...)
Definition elog.h:226
#define ereport(elevel,...)
Definition elog.h:150
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:1037
void pfree(void *pointer)
Definition mcxt.c:1616
void * palloc(Size size)
Definition mcxt.c:1387
bool process_shared_preload_libraries_in_progress
Definition miscinit.c:1787
static char * errmsg
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition palloc.h:124
#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:1275
void pgstat_reset_of_kind(PgStat_Kind kind)
Definition pgstat.c:886
bool pgstat_read_chunk(FILE *fpin, void *ptr, size_t len)
Definition pgstat.c:1763
void * pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
Definition pgstat.c:944
void pgstat_register_kind(PgStat_Kind kind, const PgStat_KindInfo *kind_info)
Definition pgstat.c:1473
void pgstat_write_chunk(FILE *fpout, void *ptr, size_t len)
Definition pgstat.c:1565
int64 PgStat_Counter
Definition pgstat.h:70
#define pgstat_write_chunk_s(fpout, ptr)
PgStat_StatsFileOp
@ STATS_WRITE
@ STATS_READ
@ STATS_DISCARD
#define pgstat_read_chunk_s(fpin, ptr)
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)
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:421
static Datum Int64GetDatum(int64 X)
Definition postgres.h:413
static Datum PointerGetDatum(const void *X)
Definition postgres.h:342
uint64_t Datum
Definition postgres.h:70
#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_to_serialized_data(const PgStat_HashKey *key, const PgStatShared_Common *header, FILE *statfile)
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)
Datum test_custom_stats_var_drop(PG_FUNCTION_ARGS)
static const PgStat_KindInfo custom_stats
Datum test_custom_stats_var_create(PG_FUNCTION_ARGS)
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)
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