Branch data 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 "storage/fd.h"
20 : : #include "utils/builtins.h"
21 : : #include "utils/pgstat_internal.h"
22 : :
23 : 3 : PG_MODULE_MAGIC_EXT(
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 */
60 : : typedef struct PgStat_StatCustomVarEntry
61 : : {
62 : : PgStat_Counter numcalls; /* times statistic was incremented */
63 : : } PgStat_StatCustomVarEntry;
64 : :
65 : : /* Shared memory statistics entry visible to all backends */
66 : : typedef struct PgStatShared_CustomVarEntry
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 */
71 : : } PgStatShared_CustomVarEntry;
72 : :
73 : : /*--------------------------------------------------------------------------
74 : : * Global Variables
75 : : *--------------------------------------------------------------------------
76 : : */
77 : :
78 : : /* File handle for auxiliary data serialization */
79 : : static FILE *fd_description = NULL;
80 : :
81 : : /* Current write offset in fd_description file */
82 : : static pgoff_t fd_description_offset = 0;
83 : :
84 : : /* DSA area for storing variable-length description strings */
85 : : static dsa_area *custom_stats_description_dsa = NULL;
86 : :
87 : : /*--------------------------------------------------------------------------
88 : : * Function prototypes
89 : : *--------------------------------------------------------------------------
90 : : */
91 : :
92 : : /* Flush callback: merge pending stats into shared memory */
93 : : static bool test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref,
94 : : bool nowait);
95 : :
96 : : /* Serialization callback: write auxiliary entry data */
97 : : static bool test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key,
98 : : const PgStatShared_Common *header,
99 : : FILE *statfile);
100 : :
101 : : /* Deserialization callback: read auxiliary entry data */
102 : : static bool test_custom_stats_var_from_serialized_data(const PgStat_HashKey *key,
103 : : PgStatShared_Common *header,
104 : : FILE *statfile);
105 : :
106 : : /* Finish callback: end of statistics file operations */
107 : : static void test_custom_stats_var_finish(PgStat_StatsFileOp status);
108 : :
109 : : /*--------------------------------------------------------------------------
110 : : * Custom kind configuration
111 : : *--------------------------------------------------------------------------
112 : : */
113 : :
114 : : static const PgStat_KindInfo custom_stats = {
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,
127 : : .finish = test_custom_stats_var_finish,
128 : : };
129 : :
130 : : /*--------------------------------------------------------------------------
131 : : * Module initialization
132 : : *--------------------------------------------------------------------------
133 : : */
134 : :
135 : : void
136 : 3 : _PG_init(void)
137 : : {
138 : : /* Register custom statistics kind */
139 : 3 : pgstat_register_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, &custom_stats);
140 : 3 : }
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 : : */
156 : : static bool
157 : 10 : test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait)
158 : : {
159 : : PgStat_StatCustomVarEntry *pending_entry;
160 : : PgStatShared_CustomVarEntry *shared_entry;
161 : :
162 : 10 : pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
163 : 10 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
164 : :
165 [ - + ]: 10 : if (!pgstat_lock_entry(entry_ref, nowait))
166 : 0 : return false;
167 : :
168 : : /* Add pending counts to shared totals */
169 : 10 : shared_entry->stats.numcalls += pending_entry->numcalls;
170 : :
171 : 10 : pgstat_unlock_entry(entry_ref);
172 : :
173 : 10 : 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 : : */
196 : : static bool
197 : 2 : test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key,
198 : : const PgStatShared_Common *header,
199 : : FILE *statfile)
200 : : {
201 : : char *description;
202 : : size_t len;
203 : 2 : const PgStatShared_CustomVarEntry *entry = (const PgStatShared_CustomVarEntry *) header;
204 : : bool found;
205 : 2 : uint32 magic_number = TEST_CUSTOM_VAR_MAGIC_NUMBER;
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 : : */
211 [ - + ]: 2 : if (!write_chunk_s(statfile, &magic_number))
212 : 0 : return false;
213 : :
214 : : /* Open statistics file for writing. */
215 [ + + ]: 2 : if (!fd_description)
216 : : {
217 : 1 : fd_description = AllocateFile(TEST_CUSTOM_AUX_DATA_DESC, PG_BINARY_W);
218 [ - + ]: 1 : if (fd_description == NULL)
219 : : {
220 [ # # ]: 0 : ereport(LOG,
221 : : (errcode_for_file_access(),
222 : : errmsg("could not open statistics file \"%s\" for writing: %m",
223 : : TEST_CUSTOM_AUX_DATA_DESC)));
224 : 0 : return false;
225 : : }
226 : :
227 : : /* Initialize offset for secondary statistics file. */
228 : 1 : fd_description_offset = 0;
229 : : }
230 : :
231 : : /* Write offset to the main data file */
232 [ - + ]: 2 : if (!write_chunk_s(statfile, &fd_description_offset))
233 : 0 : 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 : : */
240 [ - + ]: 2 : if (!write_chunk_s(fd_description, (PgStat_HashKey *) key))
241 : 0 : return false;
242 : 2 : fd_description_offset += sizeof(PgStat_HashKey);
243 : :
244 [ + + ]: 2 : if (!custom_stats_description_dsa)
245 : 1 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
246 : :
247 : : /* Handle entries without descriptions */
248 [ + - - + ]: 2 : if (!DsaPointerIsValid(entry->description) || !custom_stats_description_dsa)
249 : : {
250 : : /* length to description file */
251 : 0 : len = 0;
252 [ # # ]: 0 : if (!write_chunk_s(fd_description, &len))
253 : 0 : return false;
254 : 0 : fd_description_offset += sizeof(size_t);
255 : 0 : return true;
256 : : }
257 : :
258 : : /*
259 : : * Retrieve description from DSA, then write the length followed by the
260 : : * description.
261 : : */
262 : 2 : description = dsa_get_address(custom_stats_description_dsa,
263 : 2 : entry->description);
264 : 2 : len = strlen(description) + 1;
265 [ - + ]: 2 : if (!write_chunk_s(fd_description, &len))
266 : 0 : return false;
267 [ - + ]: 2 : if (!write_chunk(fd_description, description, len))
268 : 0 : 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 : 2 : fd_description_offset += len + sizeof(size_t);
275 : 2 : 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 : : */
288 : : static bool
289 : 2 : test_custom_stats_var_from_serialized_data(const PgStat_HashKey *key,
290 : : PgStatShared_Common *header,
291 : : FILE *statfile)
292 : : {
293 : : PgStatShared_CustomVarEntry *entry;
294 : : dsa_pointer dp;
295 : : size_t len;
296 : : pgoff_t offset;
297 : : char *buffer;
298 : : bool found;
299 : 2 : uint32 magic_number = 0;
300 : : PgStat_HashKey file_key;
301 : :
302 : : /* Check the magic number first, in the main file. */
303 [ - + ]: 2 : if (!read_chunk_s(statfile, &magic_number))
304 : : {
305 [ # # ]: 0 : elog(WARNING, "failed to read magic number from statistics file");
306 : 0 : return false;
307 : : }
308 : :
309 [ - + ]: 2 : if (magic_number != TEST_CUSTOM_VAR_MAGIC_NUMBER)
310 : : {
311 [ # # ]: 0 : elog(WARNING, "found magic number %u from statistics file, should be %u",
312 : : magic_number, TEST_CUSTOM_VAR_MAGIC_NUMBER);
313 : 0 : 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 [ - + ]: 2 : if (!read_chunk_s(statfile, &offset))
321 : : {
322 [ # # ]: 0 : elog(WARNING, "failed to read metadata offset from statistics file");
323 : 0 : return false;
324 : : }
325 : :
326 : : /* Open statistics file for reading if not already open */
327 [ + + ]: 2 : if (!fd_description)
328 : : {
329 : 1 : fd_description = AllocateFile(TEST_CUSTOM_AUX_DATA_DESC, PG_BINARY_R);
330 [ - + ]: 1 : if (fd_description == NULL)
331 : : {
332 [ # # ]: 0 : if (errno != ENOENT)
333 [ # # ]: 0 : ereport(LOG,
334 : : (errcode_for_file_access(),
335 : : errmsg("could not open statistics file \"%s\" for reading: %m",
336 : : TEST_CUSTOM_AUX_DATA_DESC)));
337 : 0 : pgstat_reset_of_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS);
338 : 0 : return false;
339 : : }
340 : : }
341 : :
342 : : /* Read data from the secondary statistics file, at the specified offset */
343 [ - + ]: 2 : if (fseeko(fd_description, offset, SEEK_SET) != 0)
344 : : {
345 [ # # ]: 0 : elog(WARNING, "could not seek in file \"%s\": %m",
346 : : TEST_CUSTOM_AUX_DATA_DESC);
347 : 0 : return false;
348 : : }
349 : :
350 : : /* Read the hash key from the secondary statistics file */
351 [ - + ]: 2 : if (!read_chunk_s(fd_description, &file_key))
352 : : {
353 [ # # ]: 0 : elog(WARNING, "failed to read hash key from file");
354 : 0 : return false;
355 : : }
356 : :
357 : : /* Check key consistency */
358 [ + - ]: 2 : if (file_key.kind != key->kind ||
359 [ + - ]: 2 : file_key.dboid != key->dboid ||
360 [ - + ]: 2 : file_key.objid != key->objid)
361 : : {
362 [ # # ]: 0 : 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 : 0 : return false;
366 : : }
367 : :
368 : 2 : entry = (PgStatShared_CustomVarEntry *) header;
369 : :
370 : : /* Read the description length and its data */
371 [ - + ]: 2 : if (!read_chunk_s(fd_description, &len))
372 : : {
373 [ # # ]: 0 : elog(WARNING, "failed to read metadata length from statistics file");
374 : 0 : return false;
375 : : }
376 : :
377 : : /* Handle empty descriptions */
378 [ - + ]: 2 : if (len == 0)
379 : : {
380 : 0 : entry->description = InvalidDsaPointer;
381 : 0 : return true;
382 : : }
383 : :
384 : : /* Initialize DSA if needed */
385 [ + + ]: 2 : if (!custom_stats_description_dsa)
386 : 1 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
387 : :
388 [ - + ]: 2 : if (!custom_stats_description_dsa)
389 : : {
390 [ # # ]: 0 : elog(WARNING, "could not access DSA for custom statistics descriptions");
391 : 0 : return false;
392 : : }
393 : :
394 : 2 : buffer = palloc(len);
395 [ - + ]: 2 : if (!read_chunk(fd_description, buffer, len))
396 : : {
397 : 0 : pfree(buffer);
398 [ # # ]: 0 : elog(WARNING, "failed to read description from file");
399 : 0 : return false;
400 : : }
401 : :
402 : : /* Allocate space in DSA and copy the description */
403 : 2 : dp = dsa_allocate(custom_stats_description_dsa, len);
404 : 2 : memcpy(dsa_get_address(custom_stats_description_dsa, dp), buffer, len);
405 : 2 : entry->description = dp;
406 : 2 : pfree(buffer);
407 : :
408 : 2 : 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 : : */
417 : : static void
418 : 4 : test_custom_stats_var_finish(PgStat_StatsFileOp status)
419 : : {
420 [ + + + - ]: 4 : switch (status)
421 : : {
422 : 1 : case STATS_WRITE:
423 [ - + ]: 1 : if (!fd_description)
424 : 0 : return;
425 : :
426 : 1 : fd_description_offset = 0;
427 : :
428 : : /* Check for write errors and cleanup if necessary */
429 [ - + ]: 1 : if (ferror(fd_description))
430 : : {
431 [ # # ]: 0 : ereport(LOG,
432 : : (errcode_for_file_access(),
433 : : errmsg("could not write to file \"%s\": %m",
434 : : TEST_CUSTOM_AUX_DATA_DESC)));
435 : 0 : FreeFile(fd_description);
436 : 0 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
437 : : }
438 [ - + ]: 1 : else if (FreeFile(fd_description) < 0)
439 : : {
440 [ # # ]: 0 : ereport(LOG,
441 : : (errcode_for_file_access(),
442 : : errmsg("could not close file \"%s\": %m",
443 : : TEST_CUSTOM_AUX_DATA_DESC)));
444 : 0 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
445 : : }
446 : 1 : break;
447 : :
448 : 2 : case STATS_READ:
449 [ + + ]: 2 : if (fd_description)
450 : 1 : FreeFile(fd_description);
451 : :
452 : : /* Remove the file after reading */
453 [ - + ]: 2 : elog(DEBUG2, "removing file \"%s\"", TEST_CUSTOM_AUX_DATA_DESC);
454 : 2 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
455 : 2 : break;
456 : :
457 : 1 : case STATS_DISCARD:
458 : : {
459 : : int ret;
460 : :
461 : : /* Attempt to remove the file */
462 : 1 : ret = unlink(TEST_CUSTOM_AUX_DATA_DESC);
463 [ + - ]: 1 : if (ret != 0)
464 : : {
465 [ + - ]: 1 : if (errno == ENOENT)
466 [ + - ]: 1 : elog(LOG,
467 : : "didn't need to unlink file \"%s\" - didn't exist",
468 : : TEST_CUSTOM_AUX_DATA_DESC);
469 : : else
470 [ # # ]: 0 : ereport(LOG,
471 : : (errcode_for_file_access(),
472 : : errmsg("could not unlink file \"%s\": %m",
473 : : TEST_CUSTOM_AUX_DATA_DESC)));
474 : : }
475 : : else
476 : : {
477 [ # # ]: 0 : ereport(LOG,
478 : : (errmsg_internal("unlinked file \"%s\"",
479 : : TEST_CUSTOM_AUX_DATA_DESC)));
480 : : }
481 : : }
482 : 1 : break;
483 : : }
484 : :
485 : 4 : fd_description = NULL;
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 : : */
499 : : static PgStat_StatCustomVarEntry *
500 : 8 : test_custom_stats_var_fetch_entry(const char *stat_name)
501 : : {
502 : : /* Fetch entry by hashed name */
503 : 8 : return (PgStat_StatCustomVarEntry *)
504 : 8 : pgstat_fetch_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS,
505 : : InvalidOid,
506 : 8 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name),
507 : : NULL);
508 : : }
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 : : */
521 : 5 : PG_FUNCTION_INFO_V1(test_custom_stats_var_create);
522 : : Datum
523 : 4 : test_custom_stats_var_create(PG_FUNCTION_ARGS)
524 : : {
525 : : PgStat_EntryRef *entry_ref;
526 : : PgStatShared_CustomVarEntry *shared_entry;
527 : 4 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
528 : 4 : char *description = text_to_cstring(PG_GETARG_TEXT_PP(1));
529 : 4 : dsa_pointer dp = InvalidDsaPointer;
530 : : bool found;
531 : :
532 : : /* Validate name length first */
533 [ - + ]: 4 : if (strlen(stat_name) >= NAMEDATALEN)
534 [ # # ]: 0 : ereport(ERROR,
535 : : (errcode(ERRCODE_NAME_TOO_LONG),
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 */
540 [ + - ]: 4 : if (!custom_stats_description_dsa)
541 : 4 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
542 : :
543 [ - + ]: 4 : if (!custom_stats_description_dsa)
544 [ # # ]: 0 : ereport(ERROR,
545 : : (errmsg("could not access DSA for custom statistics descriptions")));
546 : :
547 : : /* Allocate space in DSA and copy description */
548 : 4 : dp = dsa_allocate(custom_stats_description_dsa, strlen(description) + 1);
549 : 8 : memcpy(dsa_get_address(custom_stats_description_dsa, dp),
550 : : description,
551 : 4 : strlen(description) + 1);
552 : :
553 : : /* Create or get existing entry */
554 : 4 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
555 : 4 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), true);
556 : :
557 [ - + ]: 4 : if (!entry_ref)
558 : 0 : PG_RETURN_VOID();
559 : :
560 : 4 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
561 : :
562 : : /* Zero-initialize statistics */
563 : 4 : memset(&shared_entry->stats, 0, sizeof(shared_entry->stats));
564 : :
565 : : /* Store description pointer */
566 : 4 : shared_entry->description = dp;
567 : :
568 : 4 : pgstat_unlock_entry(entry_ref);
569 : :
570 : 4 : PG_RETURN_VOID();
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 : : */
580 : 11 : PG_FUNCTION_INFO_V1(test_custom_stats_var_update);
581 : : Datum
582 : 10 : test_custom_stats_var_update(PG_FUNCTION_ARGS)
583 : : {
584 : 10 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
585 : : PgStat_EntryRef *entry_ref;
586 : : PgStat_StatCustomVarEntry *pending_entry;
587 : :
588 : : /* Get pending entry in local memory */
589 : 10 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
590 : 10 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL);
591 : :
592 : 10 : pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
593 : 10 : pending_entry->numcalls++;
594 : :
595 : 10 : PG_RETURN_VOID();
596 : : }
597 : :
598 : : /*
599 : : * test_custom_stats_var_drop
600 : : * Remove custom statistic entry
601 : : *
602 : : * Drops the named statistic from shared memory.
603 : : */
604 : 3 : PG_FUNCTION_INFO_V1(test_custom_stats_var_drop);
605 : : Datum
606 : 2 : test_custom_stats_var_drop(PG_FUNCTION_ARGS)
607 : : {
608 : 2 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
609 : :
610 : : /* Drop entry and request GC if the entry could not be freed */
611 [ - + ]: 2 : if (!pgstat_drop_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
612 : 2 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), false))
613 : 0 : pgstat_request_entry_refs_gc();
614 : :
615 : 2 : PG_RETURN_VOID();
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 : : */
625 : 9 : PG_FUNCTION_INFO_V1(test_custom_stats_var_report);
626 : : Datum
627 : 12 : test_custom_stats_var_report(PG_FUNCTION_ARGS)
628 : : {
629 : : FuncCallContext *funcctx;
630 : : char *stat_name;
631 : : PgStat_StatCustomVarEntry *stat_entry;
632 : :
633 [ + + ]: 12 : if (SRF_IS_FIRSTCALL())
634 : : {
635 : : TupleDesc tupdesc;
636 : : MemoryContext oldcontext;
637 : :
638 : : /* Initialize SRF context */
639 : 8 : funcctx = SRF_FIRSTCALL_INIT();
640 : 8 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
641 : :
642 : : /* Get composite return type */
643 [ - + ]: 8 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
644 [ # # ]: 0 : elog(ERROR, "test_custom_stats_var_report: return type is not composite");
645 : :
646 : 8 : funcctx->tuple_desc = BlessTupleDesc(tupdesc);
647 : 8 : funcctx->max_calls = 1; /* single row result */
648 : :
649 : 8 : MemoryContextSwitchTo(oldcontext);
650 : : }
651 : :
652 : 12 : funcctx = SRF_PERCALL_SETUP();
653 : :
654 [ + + ]: 12 : if (funcctx->call_cntr < funcctx->max_calls)
655 : : {
656 : : Datum values[3];
657 : 8 : bool nulls[3] = {false, false, false};
658 : : HeapTuple tuple;
659 : : PgStat_EntryRef *entry_ref;
660 : : PgStatShared_CustomVarEntry *shared_entry;
661 : 8 : char *description = NULL;
662 : : bool found;
663 : :
664 : 8 : stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
665 : 8 : stat_entry = test_custom_stats_var_fetch_entry(stat_name);
666 : :
667 : : /* Return row only if entry exists */
668 [ + + ]: 8 : if (stat_entry)
669 : : {
670 : : /* Get entry ref to access shared entry */
671 : 4 : entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
672 : 4 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), false, NULL);
673 : :
674 [ + - ]: 4 : if (entry_ref)
675 : : {
676 : 4 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
677 : :
678 : : /* Get description from DSA if available */
679 [ + - ]: 4 : if (DsaPointerIsValid(shared_entry->description))
680 : : {
681 [ + - ]: 4 : if (!custom_stats_description_dsa)
682 : 4 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
683 : :
684 [ + - ]: 4 : if (custom_stats_description_dsa)
685 : 4 : description = dsa_get_address(custom_stats_description_dsa, shared_entry->description);
686 : : }
687 : : }
688 : :
689 : 4 : values[0] = PointerGetDatum(cstring_to_text(stat_name));
690 : 4 : values[1] = Int64GetDatum(stat_entry->numcalls);
691 : :
692 [ + - ]: 4 : if (description)
693 : 4 : values[2] = PointerGetDatum(cstring_to_text(description));
694 : : else
695 : 0 : nulls[2] = true;
696 : :
697 : 4 : tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
698 : 4 : SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
699 : : }
700 : : }
701 : :
702 : 8 : SRF_RETURN_DONE(funcctx);
703 : : }
|