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 "utils/builtins.h"
21
23 .name = "test_custom_var_stats",
24 .version = PG_VERSION
25);
26
27#define TEST_CUSTOM_VAR_MAGIC_NUMBER (0xBEEFBEEF)
28
29/*--------------------------------------------------------------------------
30 * Macros and constants
31 *--------------------------------------------------------------------------
32 */
33
34/*
35 * Kind ID for test_custom_var_stats statistics.
36 */
37#define PGSTAT_KIND_TEST_CUSTOM_VAR_STATS 25
38
39/* File paths for auxiliary data serialization */
40#define TEST_CUSTOM_AUX_DATA_DESC "pg_stat/test_custom_var_stats_desc.stats"
41
42/*
43 * Hash statistic name to generate entry index for pgstat lookup.
44 */
45#define PGSTAT_CUSTOM_VAR_STATS_IDX(name) hash_bytes_extended((const unsigned char *) name, strlen(name), 0)
46
47/*--------------------------------------------------------------------------
48 * Type definitions
49 *--------------------------------------------------------------------------
50 */
51
52/* Backend-local pending statistics before flush to shared memory */
54{
55 PgStat_Counter numcalls; /* times statistic was incremented */
57
58/* Shared memory statistics entry visible to all backends */
60{
61 PgStatShared_Common header; /* standard pgstat entry header */
62 PgStat_StatCustomVarEntry stats; /* custom statistics data */
63 dsa_pointer description; /* pointer to description string in DSA */
65
66/*--------------------------------------------------------------------------
67 * Global Variables
68 *--------------------------------------------------------------------------
69 */
70
71/* File handle for auxiliary data serialization */
73
74/* Current write offset in fd_description file */
76
77/* DSA area for storing variable-length description strings */
79
80/*--------------------------------------------------------------------------
81 * Function prototypes
82 *--------------------------------------------------------------------------
83 */
84
85/* Flush callback: merge pending stats into shared memory */
87 bool nowait);
88
89/* Serialization callback: write auxiliary entry data */
91 const PgStatShared_Common *header,
92 FILE *statfile);
93
94/* Deserialization callback: read auxiliary entry data */
96 PgStatShared_Common *header,
97 FILE *statfile);
98
99/* Finish callback: end of statistics file operations */
101
102/*--------------------------------------------------------------------------
103 * Custom kind configuration
104 *--------------------------------------------------------------------------
105 */
106
108 .name = "test_custom_var_stats",
109 .fixed_amount = false, /* variable number of entries */
110 .write_to_file = true, /* persist across restarts */
111 .track_entry_count = true, /* count active entries */
112 .accessed_across_databases = true, /* global statistics */
113 .shared_size = sizeof(PgStatShared_CustomVarEntry),
114 .shared_data_off = offsetof(PgStatShared_CustomVarEntry, stats),
115 .shared_data_len = sizeof(((PgStatShared_CustomVarEntry *) 0)->stats),
116 .pending_size = sizeof(PgStat_StatCustomVarEntry),
117 .flush_pending_cb = test_custom_stats_var_flush_pending_cb,
118 .to_serialized_data = test_custom_stats_var_to_serialized_data,
119 .from_serialized_data = test_custom_stats_var_from_serialized_data,
121};
122
123/*--------------------------------------------------------------------------
124 * Module initialization
125 *--------------------------------------------------------------------------
126 */
127
128void
130{
131 /* Must be loaded via shared_preload_libraries */
133 return;
134
135 /* Register custom statistics kind */
137}
138
139/*--------------------------------------------------------------------------
140 * Statistics callback functions
141 *--------------------------------------------------------------------------
142 */
143
144/*
145 * test_custom_stats_var_flush_pending_cb
146 * Merge pending backend statistics into shared memory
147 *
148 * Called by pgstat collector to flush accumulated local statistics
149 * to shared memory where other backends can read them.
150 *
151 * Returns false only if nowait=true and lock acquisition fails.
152 */
153static bool
155{
157 PgStatShared_CustomVarEntry *shared_entry;
158
160 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
161
162 if (!pgstat_lock_entry(entry_ref, nowait))
163 return false;
164
165 /* Add pending counts to shared totals */
166 shared_entry->stats.numcalls += pending_entry->numcalls;
167
168 pgstat_unlock_entry(entry_ref);
169
170 return true;
171}
172
173/*
174 * test_custom_stats_var_to_serialized_data() -
175 *
176 * Serialize auxiliary data (descriptions) for custom statistics entries
177 * to a secondary statistics file. This is called while writing the statistics
178 * to disk.
179 *
180 * This callback writes a mix of data within the main pgstats file and a
181 * secondary statistics file. The following data is written to the main file for
182 * each entry:
183 * - An arbitrary magic number.
184 * - An offset. This is used to know the location we need to look at
185 * to retrieve the information from the second file.
186 *
187 * The following data is written to the secondary statistics file:
188 * - The entry key, cross-checked with the data from the main file
189 * when reloaded.
190 * - The length of the description.
191 * - The description data itself.
192 */
193static void
195 const PgStatShared_Common *header,
196 FILE *statfile)
197{
198 char *description;
199 size_t len;
200 const PgStatShared_CustomVarEntry *entry = (const PgStatShared_CustomVarEntry *) header;
201 bool found;
203
204 /*
205 * First mark the main file with a magic number, keeping a trace that some
206 * auxiliary data will exist in the secondary statistics file.
207 */
209
210 /* Open statistics file for writing. */
211 if (!fd_description)
212 {
214 if (fd_description == NULL)
215 {
216 ereport(LOG,
218 errmsg("could not open statistics file \"%s\" for writing: %m",
220 return;
221 }
222
223 /* Initialize offset for secondary statistics file. */
225 }
226
227 /* Write offset to the main data file */
229
230 /*
231 * First write the entry key to the secondary statistics file. This will
232 * be cross-checked with the key read from main stats file at loading
233 * time.
234 */
237
239 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
240
241 /* Handle entries without descriptions */
243 {
244 /* length to description file */
245 len = 0;
247 fd_description_offset += sizeof(size_t);
248 return;
249 }
250
251 /*
252 * Retrieve description from DSA, then write the length followed by the
253 * description.
254 */
256 entry->description);
257 len = strlen(description) + 1;
260
261 /*
262 * Update offset for next entry, counting for the length (size_t) of the
263 * description and the description contents.
264 */
265 fd_description_offset += len + sizeof(size_t);
266}
267
268/*
269 * test_custom_stats_var_from_serialized_data() -
270 *
271 * Read auxiliary data (descriptions) for custom statistics entries from
272 * the secondary statistics file. This is called while loading the statistics
273 * at startup.
274 *
275 * See the top of test_custom_stats_var_to_serialized_data() for a
276 * detailed description of the data layout read here.
277 */
278static bool
280 PgStatShared_Common *header,
281 FILE *statfile)
282{
285 size_t len;
286 pgoff_t offset;
287 char *buffer;
288 bool found;
291
292 /* Check the magic number first, in the main file. */
294 {
295 elog(WARNING, "failed to read magic number from statistics file");
296 return false;
297 }
298
300 {
301 elog(WARNING, "found magic number %u from statistics file, should be %u",
303 return false;
304 }
305
306 /*
307 * Read the offset from the main stats file, to be able to read the
308 * auxiliary data from the secondary statistics file.
309 */
310 if (!pgstat_read_chunk_s(statfile, &offset))
311 {
312 elog(WARNING, "failed to read metadata offset from statistics file");
313 return false;
314 }
315
316 /* Open statistics file for reading if not already open */
317 if (!fd_description)
318 {
320 if (fd_description == NULL)
321 {
322 if (errno != ENOENT)
323 ereport(LOG,
325 errmsg("could not open statistics file \"%s\" for reading: %m",
328 return false;
329 }
330 }
331
332 /* Read data from the secondary statistics file, at the specified offset */
333 if (fseeko(fd_description, offset, SEEK_SET) != 0)
334 {
335 elog(WARNING, "could not seek in file \"%s\": %m",
337 return false;
338 }
339
340 /* Read the hash key from the secondary statistics file */
342 {
343 elog(WARNING, "failed to read hash key from file");
344 return false;
345 }
346
347 /* Check key consistency */
348 if (file_key.kind != key->kind ||
349 file_key.dboid != key->dboid ||
350 file_key.objid != key->objid)
351 {
352 elog(WARNING, "found entry key %u/%u/%" PRIu64 " not matching with %u/%u/%" PRIu64,
353 file_key.kind, file_key.dboid, file_key.objid,
354 key->kind, key->dboid, key->objid);
355 return false;
356 }
357
358 entry = (PgStatShared_CustomVarEntry *) header;
359
360 /* Read the description length and its data */
362 {
363 elog(WARNING, "failed to read metadata length from statistics file");
364 return false;
365 }
366
367 /* Handle empty descriptions */
368 if (len == 0)
369 {
371 return true;
372 }
373
374 /* Initialize DSA if needed */
376 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
377
379 {
380 elog(WARNING, "could not access DSA for custom statistics descriptions");
381 return false;
382 }
383
384 buffer = palloc(len);
385 if (!pgstat_read_chunk(fd_description, buffer, len))
386 {
387 pfree(buffer);
388 elog(WARNING, "failed to read description from file");
389 return false;
390 }
391
392 /* Allocate space in DSA and copy the description */
395 entry->description = dp;
396 pfree(buffer);
397
398 return true;
399}
400
401/*
402 * test_custom_stats_var_finish() -
403 *
404 * Cleanup function called at the end of statistics file operations.
405 * Handles closing files and cleanup based on the operation type.
406 */
407static void
409{
410 switch (status)
411 {
412 case STATS_WRITE:
413 if (!fd_description)
414 return;
415
417
418 /* Check for write errors and cleanup if necessary */
420 {
421 ereport(LOG,
423 errmsg("could not write to file \"%s\": %m",
427 }
428 else if (FreeFile(fd_description) < 0)
429 {
430 ereport(LOG,
432 errmsg("could not close file \"%s\": %m",
435 }
436 break;
437
438 case STATS_READ:
439 if (fd_description)
441
442 /* Remove the file after reading */
443 elog(DEBUG2, "removing file \"%s\"", TEST_CUSTOM_AUX_DATA_DESC);
445 break;
446
447 case STATS_DISCARD:
448 {
449 int ret;
450
451 /* Attempt to remove the file */
453 if (ret != 0)
454 {
455 if (errno == ENOENT)
456 elog(LOG,
457 "didn't need to unlink file \"%s\" - didn't exist",
459 else
460 ereport(LOG,
462 errmsg("could not unlink file \"%s\": %m",
464 }
465 else
466 {
467 ereport(LOG,
468 (errmsg_internal("unlinked file \"%s\"",
470 }
471 }
472 break;
473 }
474
476}
477
478/*--------------------------------------------------------------------------
479 * Helper functions
480 *--------------------------------------------------------------------------
481 */
482
483/*
484 * test_custom_stats_var_fetch_entry
485 * Look up custom statistic by name
486 *
487 * Returns statistics entry from shared memory, or NULL if not found.
488 */
498
499/*--------------------------------------------------------------------------
500 * SQL-callable functions
501 *--------------------------------------------------------------------------
502 */
503
504/*
505 * test_custom_stats_var_create
506 * Create new custom statistic entry
507 *
508 * Initializes a statistics entry with the given name and description.
509 */
511Datum
513{
514 PgStat_EntryRef *entry_ref;
515 PgStatShared_CustomVarEntry *shared_entry;
519 bool found;
520
521 /* Validate name length first */
525 errmsg("custom statistic name \"%s\" is too long", stat_name),
526 errdetail("Name must be less than %d characters.", NAMEDATALEN)));
527
528 /* Initialize DSA and description provided */
530 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
531
534 (errmsg("could not access DSA for custom statistics descriptions")));
535
536 /* Allocate space in DSA and copy description */
540 strlen(description) + 1);
541
542 /* Create or get existing entry */
545
546 if (!entry_ref)
548
549 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
550
551 /* Zero-initialize statistics */
552 memset(&shared_entry->stats, 0, sizeof(shared_entry->stats));
553
554 /* Store description pointer */
555 shared_entry->description = dp;
556
557 pgstat_unlock_entry(entry_ref);
558
560}
561
562/*
563 * test_custom_stats_var_update
564 * Increment custom statistic counter
565 *
566 * Increments call count in backend-local memory. Changes are flushed
567 * to shared memory by the statistics collector.
568 */
570Datum
586
587/*
588 * test_custom_stats_var_drop
589 * Remove custom statistic entry
590 *
591 * Drops the named statistic from shared memory.
592 */
594Datum
606
607/*
608 * test_custom_stats_var_report
609 * Retrieve custom statistic values
610 *
611 * Returns single row with statistic name, call count, and description if the
612 * statistic exists, otherwise returns no rows.
613 */
615Datum
617{
619 char *stat_name;
621
622 if (SRF_IS_FIRSTCALL())
623 {
624 TupleDesc tupdesc;
625 MemoryContext oldcontext;
626
627 /* Initialize SRF context */
629 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
630
631 /* Get composite return type */
632 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
633 elog(ERROR, "test_custom_stats_var_report: return type is not composite");
634
635 funcctx->tuple_desc = BlessTupleDesc(tupdesc);
636 funcctx->max_calls = 1; /* single row result */
637
638 MemoryContextSwitchTo(oldcontext);
639 }
640
642
643 if (funcctx->call_cntr < funcctx->max_calls)
644 {
645 Datum values[3];
646 bool nulls[3] = {false, false, false};
647 HeapTuple tuple;
648 PgStat_EntryRef *entry_ref;
649 PgStatShared_CustomVarEntry *shared_entry;
650 char *description = NULL;
651 bool found;
652
655
656 /* Return row only if entry exists */
657 if (stat_entry)
658 {
659 /* Get entry ref to access shared entry */
662
663 if (entry_ref)
664 {
665 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
666
667 /* Get description from DSA if available */
668 if (DsaPointerIsValid(shared_entry->description))
669 {
671 custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
672
675 }
676 }
677
679 values[1] = Int64GetDatum(stat_entry->numcalls);
680
681 if (description)
683 else
684 nulls[2] = true;
685
686 tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
688 }
689 }
690
692}
static Datum values[MAXATTR]
Definition bootstrap.c:147
#define PG_BINARY_R
Definition c.h:1311
uint32_t uint32
Definition c.h:558
#define PG_BINARY_W
Definition c.h:1312
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
int errmsg(const char *fmt,...)
Definition elog.c:1093
#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:2826
FILE * AllocateFile(const char *name, const char *mode)
Definition fd.c:2627
#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:1117
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:1786
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:71
#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:423
static Datum PointerGetDatum(const void *X)
Definition postgres.h:352
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:182
char * text_to_cstring(const text *t)
Definition varlena.c:215
const char * description
const char * name
#define fseeko(stream, offset, origin)
Definition win32_port.h:206