Age Owner Branch data TLA Line data Source code
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"
18 : : #include "storage/dsm_registry.h"
19 : : #include "utils/builtins.h"
20 : : #include "utils/pgstat_internal.h"
21 : :
97 michael@paquier.xyz 22 :GNC 3 : PG_MODULE_MAGIC_EXT(
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 */
53 : : typedef struct PgStat_StatCustomVarEntry
54 : : {
55 : : PgStat_Counter numcalls; /* times statistic was incremented */
56 : : } PgStat_StatCustomVarEntry;
57 : :
58 : : /* Shared memory statistics entry visible to all backends */
59 : : typedef struct PgStatShared_CustomVarEntry
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 */
64 : : } PgStatShared_CustomVarEntry;
65 : :
66 : : /*--------------------------------------------------------------------------
67 : : * Global Variables
68 : : *--------------------------------------------------------------------------
69 : : */
70 : :
71 : : /* File handle for auxiliary data serialization */
72 : : static FILE *fd_description = NULL;
73 : :
74 : : /* Current write offset in fd_description file */
75 : : static pgoff_t fd_description_offset = 0;
76 : :
77 : : /* DSA area for storing variable-length description strings */
78 : : static dsa_area *custom_stats_description_dsa = NULL;
79 : :
80 : : /*--------------------------------------------------------------------------
81 : : * Function prototypes
82 : : *--------------------------------------------------------------------------
83 : : */
84 : :
85 : : /* Flush callback: merge pending stats into shared memory */
86 : : static bool test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref,
87 : : bool nowait);
88 : :
89 : : /* Serialization callback: write auxiliary entry data */
90 : : static void test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key,
91 : : const PgStatShared_Common *header,
92 : : FILE *statfile);
93 : :
94 : : /* Deserialization callback: read auxiliary entry data */
95 : : static bool test_custom_stats_var_from_serialized_data(const PgStat_HashKey *key,
96 : : PgStatShared_Common *header,
97 : : FILE *statfile);
98 : :
99 : : /* Finish callback: end of statistics file operations */
100 : : static void test_custom_stats_var_finish(PgStat_StatsFileOp status);
101 : :
102 : : /*--------------------------------------------------------------------------
103 : : * Custom kind configuration
104 : : *--------------------------------------------------------------------------
105 : : */
106 : :
107 : : static const PgStat_KindInfo custom_stats = {
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,
120 : : .finish = test_custom_stats_var_finish,
121 : : };
122 : :
123 : : /*--------------------------------------------------------------------------
124 : : * Module initialization
125 : : *--------------------------------------------------------------------------
126 : : */
127 : :
128 : : void
129 : 3 : _PG_init(void)
130 : : {
131 : : /* Must be loaded via shared_preload_libraries */
132 [ - + ]: 3 : if (!process_shared_preload_libraries_in_progress)
97 michael@paquier.xyz 133 :UNC 0 : return;
134 : :
135 : : /* Register custom statistics kind */
97 michael@paquier.xyz 136 :GNC 3 : pgstat_register_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, &custom_stats);
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 : : */
153 : : static bool
154 : 10 : test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait)
155 : : {
156 : : PgStat_StatCustomVarEntry *pending_entry;
157 : : PgStatShared_CustomVarEntry *shared_entry;
158 : :
159 : 10 : pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
160 : 10 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
161 : :
162 [ - + ]: 10 : if (!pgstat_lock_entry(entry_ref, nowait))
97 michael@paquier.xyz 163 :UNC 0 : return false;
164 : :
165 : : /* Add pending counts to shared totals */
97 michael@paquier.xyz 166 :GNC 10 : shared_entry->stats.numcalls += pending_entry->numcalls;
167 : :
168 : 10 : pgstat_unlock_entry(entry_ref);
169 : :
170 : 10 : 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 : : */
193 : : static void
90 194 : 2 : test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key,
195 : : const PgStatShared_Common *header,
196 : : FILE *statfile)
197 : : {
198 : : char *description;
199 : : size_t len;
87 200 : 2 : const PgStatShared_CustomVarEntry *entry = (const PgStatShared_CustomVarEntry *) header;
201 : : bool found;
90 202 : 2 : uint32 magic_number = TEST_CUSTOM_VAR_MAGIC_NUMBER;
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 : : */
208 : 2 : pgstat_write_chunk_s(statfile, &magic_number);
209 : :
210 : : /* Open statistics file for writing. */
211 [ + + ]: 2 : if (!fd_description)
212 : : {
213 : 1 : fd_description = AllocateFile(TEST_CUSTOM_AUX_DATA_DESC, PG_BINARY_W);
214 [ - + ]: 1 : if (fd_description == NULL)
215 : : {
90 michael@paquier.xyz 216 [ # # ]:UNC 0 : ereport(LOG,
217 : : (errcode_for_file_access(),
218 : : errmsg("could not open statistics file \"%s\" for writing: %m",
219 : : TEST_CUSTOM_AUX_DATA_DESC)));
220 : 0 : return;
221 : : }
222 : :
223 : : /* Initialize offset for secondary statistics file. */
90 michael@paquier.xyz 224 :GNC 1 : fd_description_offset = 0;
225 : : }
226 : :
227 : : /* Write offset to the main data file */
228 : 2 : pgstat_write_chunk_s(statfile, &fd_description_offset);
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 : : */
235 : 2 : pgstat_write_chunk_s(fd_description, (PgStat_HashKey *) key);
236 : 2 : fd_description_offset += sizeof(PgStat_HashKey);
237 : :
238 [ + + ]: 2 : if (!custom_stats_description_dsa)
239 : 1 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
240 : :
241 : : /* Handle entries without descriptions */
242 [ + - - + ]: 2 : if (!DsaPointerIsValid(entry->description) || !custom_stats_description_dsa)
243 : : {
244 : : /* length to description file */
90 michael@paquier.xyz 245 :UNC 0 : len = 0;
246 : 0 : pgstat_write_chunk_s(fd_description, &len);
247 : 0 : fd_description_offset += sizeof(size_t);
248 : 0 : return;
249 : : }
250 : :
251 : : /*
252 : : * Retrieve description from DSA, then write the length followed by the
253 : : * description.
254 : : */
90 michael@paquier.xyz 255 :GNC 2 : description = dsa_get_address(custom_stats_description_dsa,
256 : 2 : entry->description);
257 : 2 : len = strlen(description) + 1;
258 : 2 : pgstat_write_chunk_s(fd_description, &len);
259 : 2 : pgstat_write_chunk(fd_description, description, len);
260 : :
261 : : /*
262 : : * Update offset for next entry, counting for the length (size_t) of the
263 : : * description and the description contents.
264 : : */
265 : 2 : 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 : : */
278 : : static bool
279 : 2 : test_custom_stats_var_from_serialized_data(const PgStat_HashKey *key,
280 : : PgStatShared_Common *header,
281 : : FILE *statfile)
282 : : {
283 : : PgStatShared_CustomVarEntry *entry;
284 : : dsa_pointer dp;
285 : : size_t len;
286 : : pgoff_t offset;
287 : : char *buffer;
288 : : bool found;
289 : 2 : uint32 magic_number = 0;
290 : : PgStat_HashKey file_key;
291 : :
292 : : /* Check the magic number first, in the main file. */
293 [ - + ]: 2 : if (!pgstat_read_chunk_s(statfile, &magic_number))
294 : : {
90 michael@paquier.xyz 295 [ # # ]:UNC 0 : elog(WARNING, "failed to read magic number from statistics file");
296 : 0 : return false;
297 : : }
298 : :
90 michael@paquier.xyz 299 [ - + ]:GNC 2 : if (magic_number != TEST_CUSTOM_VAR_MAGIC_NUMBER)
300 : : {
90 michael@paquier.xyz 301 [ # # ]:UNC 0 : elog(WARNING, "found magic number %u from statistics file, should be %u",
302 : : magic_number, TEST_CUSTOM_VAR_MAGIC_NUMBER);
303 : 0 : 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 : : */
90 michael@paquier.xyz 310 [ - + ]:GNC 2 : if (!pgstat_read_chunk_s(statfile, &offset))
311 : : {
90 michael@paquier.xyz 312 [ # # ]:UNC 0 : elog(WARNING, "failed to read metadata offset from statistics file");
313 : 0 : return false;
314 : : }
315 : :
316 : : /* Open statistics file for reading if not already open */
90 michael@paquier.xyz 317 [ + + ]:GNC 2 : if (!fd_description)
318 : : {
319 : 1 : fd_description = AllocateFile(TEST_CUSTOM_AUX_DATA_DESC, PG_BINARY_R);
320 [ - + ]: 1 : if (fd_description == NULL)
321 : : {
90 michael@paquier.xyz 322 [ # # ]:UNC 0 : if (errno != ENOENT)
323 [ # # ]: 0 : ereport(LOG,
324 : : (errcode_for_file_access(),
325 : : errmsg("could not open statistics file \"%s\" for reading: %m",
326 : : TEST_CUSTOM_AUX_DATA_DESC)));
327 : 0 : pgstat_reset_of_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS);
328 : 0 : return false;
329 : : }
330 : : }
331 : :
332 : : /* Read data from the secondary statistics file, at the specified offset */
90 michael@paquier.xyz 333 [ - + ]:GNC 2 : if (fseeko(fd_description, offset, SEEK_SET) != 0)
334 : : {
90 michael@paquier.xyz 335 [ # # ]:UNC 0 : elog(WARNING, "could not seek in file \"%s\": %m",
336 : : TEST_CUSTOM_AUX_DATA_DESC);
337 : 0 : return false;
338 : : }
339 : :
340 : : /* Read the hash key from the secondary statistics file */
90 michael@paquier.xyz 341 [ - + ]:GNC 2 : if (!pgstat_read_chunk_s(fd_description, &file_key))
342 : : {
90 michael@paquier.xyz 343 [ # # ]:UNC 0 : elog(WARNING, "failed to read hash key from file");
344 : 0 : return false;
345 : : }
346 : :
347 : : /* Check key consistency */
90 michael@paquier.xyz 348 [ + - ]:GNC 2 : if (file_key.kind != key->kind ||
349 [ + - ]: 2 : file_key.dboid != key->dboid ||
350 [ - + ]: 2 : file_key.objid != key->objid)
351 : : {
90 michael@paquier.xyz 352 [ # # ]:UNC 0 : 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 : 0 : return false;
356 : : }
357 : :
90 michael@paquier.xyz 358 :GNC 2 : entry = (PgStatShared_CustomVarEntry *) header;
359 : :
360 : : /* Read the description length and its data */
361 [ - + ]: 2 : if (!pgstat_read_chunk_s(fd_description, &len))
362 : : {
90 michael@paquier.xyz 363 [ # # ]:UNC 0 : elog(WARNING, "failed to read metadata length from statistics file");
364 : 0 : return false;
365 : : }
366 : :
367 : : /* Handle empty descriptions */
90 michael@paquier.xyz 368 [ - + ]:GNC 2 : if (len == 0)
369 : : {
90 michael@paquier.xyz 370 :UNC 0 : entry->description = InvalidDsaPointer;
371 : 0 : return true;
372 : : }
373 : :
374 : : /* Initialize DSA if needed */
90 michael@paquier.xyz 375 [ + + ]:GNC 2 : if (!custom_stats_description_dsa)
376 : 1 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
377 : :
378 [ - + ]: 2 : if (!custom_stats_description_dsa)
379 : : {
90 michael@paquier.xyz 380 [ # # ]:UNC 0 : elog(WARNING, "could not access DSA for custom statistics descriptions");
381 : 0 : return false;
382 : : }
383 : :
90 michael@paquier.xyz 384 :GNC 2 : buffer = palloc(len);
385 [ - + ]: 2 : if (!pgstat_read_chunk(fd_description, buffer, len))
386 : : {
90 michael@paquier.xyz 387 :UNC 0 : pfree(buffer);
388 [ # # ]: 0 : elog(WARNING, "failed to read description from file");
389 : 0 : return false;
390 : : }
391 : :
392 : : /* Allocate space in DSA and copy the description */
90 michael@paquier.xyz 393 :GNC 2 : dp = dsa_allocate(custom_stats_description_dsa, len);
394 : 2 : memcpy(dsa_get_address(custom_stats_description_dsa, dp), buffer, len);
395 : 2 : entry->description = dp;
396 : 2 : pfree(buffer);
397 : :
398 : 2 : 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 : : */
407 : : static void
408 : 4 : test_custom_stats_var_finish(PgStat_StatsFileOp status)
409 : : {
410 [ + + + - ]: 4 : switch (status)
411 : : {
412 : 1 : case STATS_WRITE:
413 [ - + ]: 1 : if (!fd_description)
90 michael@paquier.xyz 414 :UNC 0 : return;
415 : :
90 michael@paquier.xyz 416 :GNC 1 : fd_description_offset = 0;
417 : :
418 : : /* Check for write errors and cleanup if necessary */
419 [ - + ]: 1 : if (ferror(fd_description))
420 : : {
90 michael@paquier.xyz 421 [ # # ]:UNC 0 : ereport(LOG,
422 : : (errcode_for_file_access(),
423 : : errmsg("could not write to file \"%s\": %m",
424 : : TEST_CUSTOM_AUX_DATA_DESC)));
425 : 0 : FreeFile(fd_description);
426 : 0 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
427 : : }
90 michael@paquier.xyz 428 [ - + ]:GNC 1 : else if (FreeFile(fd_description) < 0)
429 : : {
90 michael@paquier.xyz 430 [ # # ]:UNC 0 : ereport(LOG,
431 : : (errcode_for_file_access(),
432 : : errmsg("could not close file \"%s\": %m",
433 : : TEST_CUSTOM_AUX_DATA_DESC)));
434 : 0 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
435 : : }
90 michael@paquier.xyz 436 :GNC 1 : break;
437 : :
438 : 2 : case STATS_READ:
439 [ + + ]: 2 : if (fd_description)
440 : 1 : FreeFile(fd_description);
441 : :
442 : : /* Remove the file after reading */
443 [ - + ]: 2 : elog(DEBUG2, "removing file \"%s\"", TEST_CUSTOM_AUX_DATA_DESC);
444 : 2 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
445 : 2 : break;
446 : :
447 : 1 : case STATS_DISCARD:
448 : : {
449 : : int ret;
450 : :
451 : : /* Attempt to remove the file */
452 : 1 : ret = unlink(TEST_CUSTOM_AUX_DATA_DESC);
453 [ + - ]: 1 : if (ret != 0)
454 : : {
455 [ + - ]: 1 : if (errno == ENOENT)
456 [ + - ]: 1 : elog(LOG,
457 : : "didn't need to unlink file \"%s\" - didn't exist",
458 : : TEST_CUSTOM_AUX_DATA_DESC);
459 : : else
90 michael@paquier.xyz 460 [ # # ]:UNC 0 : ereport(LOG,
461 : : (errcode_for_file_access(),
462 : : errmsg("could not unlink file \"%s\": %m",
463 : : TEST_CUSTOM_AUX_DATA_DESC)));
464 : : }
465 : : else
466 : : {
467 [ # # ]: 0 : ereport(LOG,
468 : : (errmsg_internal("unlinked file \"%s\"",
469 : : TEST_CUSTOM_AUX_DATA_DESC)));
470 : : }
471 : : }
90 michael@paquier.xyz 472 :GNC 1 : break;
473 : : }
474 : :
475 : 4 : fd_description = NULL;
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 : : */
489 : : static PgStat_StatCustomVarEntry *
97 490 : 9 : test_custom_stats_var_fetch_entry(const char *stat_name)
491 : : {
492 : : /* Fetch entry by hashed name */
493 : 9 : return (PgStat_StatCustomVarEntry *)
494 : 9 : pgstat_fetch_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS,
495 : : InvalidOid,
496 : 9 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name));
497 : : }
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 : : */
510 : 5 : PG_FUNCTION_INFO_V1(test_custom_stats_var_create);
511 : : Datum
512 : 4 : test_custom_stats_var_create(PG_FUNCTION_ARGS)
513 : : {
514 : : PgStat_EntryRef *entry_ref;
515 : : PgStatShared_CustomVarEntry *shared_entry;
516 : 4 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
90 517 : 4 : char *description = text_to_cstring(PG_GETARG_TEXT_PP(1));
518 : 4 : dsa_pointer dp = InvalidDsaPointer;
519 : : bool found;
520 : :
521 : : /* Validate name length first */
97 522 [ - + ]: 4 : if (strlen(stat_name) >= NAMEDATALEN)
97 michael@paquier.xyz 523 [ # # ]:UNC 0 : ereport(ERROR,
524 : : (errcode(ERRCODE_NAME_TOO_LONG),
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 */
90 michael@paquier.xyz 529 [ + - ]:GNC 4 : if (!custom_stats_description_dsa)
530 : 4 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
531 : :
532 [ - + ]: 4 : if (!custom_stats_description_dsa)
90 michael@paquier.xyz 533 [ # # ]:UNC 0 : ereport(ERROR,
534 : : (errmsg("could not access DSA for custom statistics descriptions")));
535 : :
536 : : /* Allocate space in DSA and copy description */
90 michael@paquier.xyz 537 :GNC 4 : dp = dsa_allocate(custom_stats_description_dsa, strlen(description) + 1);
538 : 8 : memcpy(dsa_get_address(custom_stats_description_dsa, dp),
539 : : description,
540 : 4 : strlen(description) + 1);
541 : :
542 : : /* Create or get existing entry */
97 543 : 4 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
544 : 4 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), true);
545 : :
546 [ - + ]: 4 : if (!entry_ref)
97 michael@paquier.xyz 547 :UNC 0 : PG_RETURN_VOID();
548 : :
97 michael@paquier.xyz 549 :GNC 4 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
550 : :
551 : : /* Zero-initialize statistics */
552 : 4 : memset(&shared_entry->stats, 0, sizeof(shared_entry->stats));
553 : :
554 : : /* Store description pointer */
90 555 : 4 : shared_entry->description = dp;
556 : :
97 557 : 4 : pgstat_unlock_entry(entry_ref);
558 : :
559 : 4 : PG_RETURN_VOID();
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 : : */
569 : 11 : PG_FUNCTION_INFO_V1(test_custom_stats_var_update);
570 : : Datum
571 : 10 : test_custom_stats_var_update(PG_FUNCTION_ARGS)
572 : : {
573 : 10 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
574 : : PgStat_EntryRef *entry_ref;
575 : : PgStat_StatCustomVarEntry *pending_entry;
576 : :
577 : : /* Get pending entry in local memory */
578 : 10 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
579 : 10 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL);
580 : :
581 : 10 : pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
582 : 10 : pending_entry->numcalls++;
583 : :
584 : 10 : PG_RETURN_VOID();
585 : : }
586 : :
587 : : /*
588 : : * test_custom_stats_var_drop
589 : : * Remove custom statistic entry
590 : : *
591 : : * Drops the named statistic from shared memory.
592 : : */
593 : 3 : PG_FUNCTION_INFO_V1(test_custom_stats_var_drop);
594 : : Datum
595 : 2 : test_custom_stats_var_drop(PG_FUNCTION_ARGS)
596 : : {
597 : 2 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
598 : :
599 : : /* Drop entry and request GC if the entry could not be freed */
600 [ - + ]: 2 : if (!pgstat_drop_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
601 : 2 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name)))
97 michael@paquier.xyz 602 :UNC 0 : pgstat_request_entry_refs_gc();
603 : :
97 michael@paquier.xyz 604 :GNC 2 : PG_RETURN_VOID();
605 : : }
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 : : */
614 : 10 : PG_FUNCTION_INFO_V1(test_custom_stats_var_report);
615 : : Datum
616 : 14 : test_custom_stats_var_report(PG_FUNCTION_ARGS)
617 : : {
618 : : FuncCallContext *funcctx;
619 : : char *stat_name;
620 : : PgStat_StatCustomVarEntry *stat_entry;
621 : :
622 [ + + ]: 14 : if (SRF_IS_FIRSTCALL())
623 : : {
624 : : TupleDesc tupdesc;
625 : : MemoryContext oldcontext;
626 : :
627 : : /* Initialize SRF context */
628 : 9 : funcctx = SRF_FIRSTCALL_INIT();
629 : 9 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
630 : :
631 : : /* Get composite return type */
632 [ - + ]: 9 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
97 michael@paquier.xyz 633 [ # # ]:UNC 0 : elog(ERROR, "test_custom_stats_var_report: return type is not composite");
634 : :
97 michael@paquier.xyz 635 :GNC 9 : funcctx->tuple_desc = BlessTupleDesc(tupdesc);
636 : 9 : funcctx->max_calls = 1; /* single row result */
637 : :
638 : 9 : MemoryContextSwitchTo(oldcontext);
639 : : }
640 : :
641 : 14 : funcctx = SRF_PERCALL_SETUP();
642 : :
643 [ + + ]: 14 : if (funcctx->call_cntr < funcctx->max_calls)
644 : : {
645 : : Datum values[3];
90 646 : 9 : bool nulls[3] = {false, false, false};
647 : : HeapTuple tuple;
648 : : PgStat_EntryRef *entry_ref;
649 : : PgStatShared_CustomVarEntry *shared_entry;
650 : 9 : char *description = NULL;
651 : : bool found;
652 : :
97 653 : 9 : stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
654 : 9 : stat_entry = test_custom_stats_var_fetch_entry(stat_name);
655 : :
656 : : /* Return row only if entry exists */
657 [ + + ]: 9 : if (stat_entry)
658 : : {
659 : : /* Get entry ref to access shared entry */
90 660 : 5 : entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
661 : 5 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), false, NULL);
662 : :
663 [ + - ]: 5 : if (entry_ref)
664 : : {
665 : 5 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
666 : :
667 : : /* Get description from DSA if available */
668 [ + - ]: 5 : if (DsaPointerIsValid(shared_entry->description))
669 : : {
670 [ + - ]: 5 : if (!custom_stats_description_dsa)
671 : 5 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
672 : :
673 [ + - ]: 5 : if (custom_stats_description_dsa)
674 : 5 : description = dsa_get_address(custom_stats_description_dsa, shared_entry->description);
675 : : }
676 : : }
677 : :
97 678 : 5 : values[0] = PointerGetDatum(cstring_to_text(stat_name));
679 : 5 : values[1] = Int64GetDatum(stat_entry->numcalls);
680 : :
90 681 [ + - ]: 5 : if (description)
682 : 5 : values[2] = PointerGetDatum(cstring_to_text(description));
683 : : else
90 michael@paquier.xyz 684 :UNC 0 : nulls[2] = true;
685 : :
97 michael@paquier.xyz 686 :GNC 5 : tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
687 : 5 : SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
688 : : }
689 : : }
690 : :
691 : 9 : SRF_RETURN_DONE(funcctx);
692 : : }
|