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/guc.h"
22 : : #include "utils/pgstat_internal.h"
23 : :
24 : 9 : PG_MODULE_MAGIC_EXT(
25 : : .name = "test_custom_var_stats",
26 : : .version = PG_VERSION
27 : : );
28 : :
29 : : /* Local helpers for stats file I/O */
30 : : #define write_chunk(fpout, ptr, len) (fwrite(ptr, len, 1, fpout) == 1)
31 : : #define write_chunk_s(fpout, ptr) write_chunk(fpout, ptr, sizeof(*ptr))
32 : : #define read_chunk(fpin, ptr, len) (fread(ptr, 1, len, fpin) == (len))
33 : : #define read_chunk_s(fpin, ptr) read_chunk(fpin, ptr, sizeof(*ptr))
34 : :
35 : : #define TEST_CUSTOM_VAR_MAGIC_NUMBER (0xBEEFBEEF)
36 : :
37 : : /*--------------------------------------------------------------------------
38 : : * Macros and constants
39 : : *--------------------------------------------------------------------------
40 : : */
41 : :
42 : : /*
43 : : * Kind ID for test_custom_var_stats statistics.
44 : : */
45 : : #define PGSTAT_KIND_TEST_CUSTOM_VAR_STATS 25
46 : :
47 : : /* File paths for auxiliary data serialization */
48 : : #define TEST_CUSTOM_AUX_DATA_DESC "pg_stat/test_custom_var_stats_desc.stats"
49 : :
50 : : /*
51 : : * Hash statistic name to generate entry index for pgstat lookup.
52 : : */
53 : : #define PGSTAT_CUSTOM_VAR_STATS_IDX(name) hash_bytes_extended((const unsigned char *) name, strlen(name), 0)
54 : :
55 : : /*--------------------------------------------------------------------------
56 : : * Type definitions
57 : : *--------------------------------------------------------------------------
58 : : */
59 : :
60 : : /* Backend-local pending statistics before flush to shared memory */
61 : : typedef struct PgStat_StatCustomVarEntry
62 : : {
63 : : PgStat_Counter numcalls; /* times statistic was incremented */
64 : : } PgStat_StatCustomVarEntry;
65 : :
66 : : /* Shared memory statistics entry visible to all backends */
67 : : typedef struct PgStatShared_CustomVarEntry
68 : : {
69 : : PgStatShared_Common header; /* standard pgstat entry header */
70 : : PgStat_StatCustomVarEntry stats; /* custom statistics data */
71 : : dsa_pointer description; /* pointer to description string in DSA */
72 : : } PgStatShared_CustomVarEntry;
73 : :
74 : : /*--------------------------------------------------------------------------
75 : : * Global Variables
76 : : *--------------------------------------------------------------------------
77 : : */
78 : :
79 : : /* File handle for auxiliary data serialization */
80 : : static FILE *fd_description = NULL;
81 : :
82 : : /* Current write offset in fd_description file */
83 : : static pgoff_t fd_description_offset = 0;
84 : :
85 : : /* DSA area for storing variable-length description strings */
86 : : static dsa_area *custom_stats_description_dsa = NULL;
87 : :
88 : : /*--------------------------------------------------------------------------
89 : : * Function prototypes
90 : : *--------------------------------------------------------------------------
91 : : */
92 : :
93 : : /* Flush callback: merge pending stats into shared memory */
94 : : static bool test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref,
95 : : bool nowait);
96 : :
97 : : /* Serialization callback: write auxiliary entry data */
98 : : static bool test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key,
99 : : const PgStatShared_Common *header,
100 : : FILE *statfile);
101 : :
102 : : /* Deserialization callback: read auxiliary entry data */
103 : : static bool test_custom_stats_var_from_serialized_data(const PgStat_HashKey *key,
104 : : PgStatShared_Common *header,
105 : : FILE *statfile);
106 : :
107 : : /* Finish callback: end of statistics file operations */
108 : : static void test_custom_stats_var_finish(PgStat_StatsFileOp status);
109 : :
110 : : /*--------------------------------------------------------------------------
111 : : * Custom kind configuration
112 : : *--------------------------------------------------------------------------
113 : : */
114 : :
115 : : /* Which variable-sized custom stats kind definition to register. */
116 : : static char *test_custom_stats_kind = "test_custom_var_stats";
117 : :
118 : : static const PgStat_KindInfo custom_stats_own_hash = {
119 : : .name = "test_custom_var_stats_own_hash",
120 : : .fixed_amount = false, /* variable number of entries */
121 : : .write_to_file = true, /* persist across restarts */
122 : : .track_entry_count = true, /* count active entries */
123 : : .accessed_across_databases = true, /* global statistics */
124 : : .own_hash = true, /* use dedicated dshash */
125 : : .shared_size = sizeof(PgStatShared_CustomVarEntry),
126 : : .shared_data_off = offsetof(PgStatShared_CustomVarEntry, stats),
127 : : .shared_data_len = sizeof(((PgStatShared_CustomVarEntry *) 0)->stats),
128 : : .pending_size = sizeof(PgStat_StatCustomVarEntry),
129 : : .flush_pending_cb = test_custom_stats_var_flush_pending_cb,
130 : : .to_serialized_data = test_custom_stats_var_to_serialized_data,
131 : : .from_serialized_data = test_custom_stats_var_from_serialized_data,
132 : : .finish = test_custom_stats_var_finish,
133 : : };
134 : :
135 : : static const PgStat_KindInfo custom_stats_shared_hash = {
136 : : .name = "test_custom_var_stats",
137 : : .fixed_amount = false, /* variable number of entries */
138 : : .write_to_file = true, /* persist across restarts */
139 : : .track_entry_count = true, /* count active entries */
140 : : .accessed_across_databases = true, /* global statistics */
141 : : .shared_size = sizeof(PgStatShared_CustomVarEntry),
142 : : .shared_data_off = offsetof(PgStatShared_CustomVarEntry, stats),
143 : : .shared_data_len = sizeof(((PgStatShared_CustomVarEntry *) 0)->stats),
144 : : .pending_size = sizeof(PgStat_StatCustomVarEntry),
145 : : .flush_pending_cb = test_custom_stats_var_flush_pending_cb,
146 : : .to_serialized_data = test_custom_stats_var_to_serialized_data,
147 : : .from_serialized_data = test_custom_stats_var_from_serialized_data,
148 : : .finish = test_custom_stats_var_finish,
149 : : };
150 : :
151 : : /*--------------------------------------------------------------------------
152 : : * Module initialization
153 : : *--------------------------------------------------------------------------
154 : : */
155 : :
156 : : void
157 : 9 : _PG_init(void)
158 : : {
159 : 9 : DefineCustomStringVariable("test_custom_var_stats.kind",
160 : : "Sets the custom variable stats kind to register.",
161 : : NULL,
162 : : &test_custom_stats_kind,
163 : : "test_custom_var_stats",
164 : : PGC_POSTMASTER,
165 : : 0,
166 : : NULL, NULL, NULL);
167 : :
168 [ + + ]: 9 : if (strcmp(test_custom_stats_kind, custom_stats_shared_hash.name) == 0)
169 : 6 : pgstat_register_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS,
170 : : &custom_stats_shared_hash);
171 [ + - ]: 3 : else if (strcmp(test_custom_stats_kind, custom_stats_own_hash.name) == 0)
172 : 3 : pgstat_register_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS,
173 : : &custom_stats_own_hash);
174 : : else
175 [ # # ]: 0 : ereport(ERROR,
176 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
177 : : errmsg("invalid value for parameter \"%s\": \"%s\"",
178 : : "test_custom_var_stats.kind",
179 : : test_custom_stats_kind)));
180 : 9 : }
181 : :
182 : : /*--------------------------------------------------------------------------
183 : : * Statistics callback functions
184 : : *--------------------------------------------------------------------------
185 : : */
186 : :
187 : : /*
188 : : * test_custom_stats_var_flush_pending_cb
189 : : * Merge pending backend statistics into shared memory
190 : : *
191 : : * Called by pgstat collector to flush accumulated local statistics
192 : : * to shared memory where other backends can read them.
193 : : *
194 : : * Returns false only if nowait=true and lock acquisition fails.
195 : : */
196 : : static bool
197 : 8 : test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait)
198 : : {
199 : : PgStat_StatCustomVarEntry *pending_entry;
200 : : PgStatShared_CustomVarEntry *shared_entry;
201 : :
202 : 8 : pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
203 : 8 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
204 : :
205 [ - + ]: 8 : if (!pgstat_lock_entry(entry_ref, nowait))
206 : 0 : return false;
207 : :
208 : : /* Add pending counts to shared totals */
209 : 8 : shared_entry->stats.numcalls += pending_entry->numcalls;
210 : :
211 : 8 : pgstat_unlock_entry(entry_ref);
212 : :
213 : 8 : return true;
214 : : }
215 : :
216 : : /*
217 : : * test_custom_stats_var_to_serialized_data() -
218 : : *
219 : : * Serialize auxiliary data (descriptions) for custom statistics entries
220 : : * to a secondary statistics file. This is called while writing the statistics
221 : : * to disk.
222 : : *
223 : : * This callback writes a mix of data within the main pgstats file and a
224 : : * secondary statistics file. The following data is written to the main file for
225 : : * each entry:
226 : : * - An arbitrary magic number.
227 : : * - An offset. This is used to know the location we need to look at
228 : : * to retrieve the information from the second file.
229 : : *
230 : : * The following data is written to the secondary statistics file:
231 : : * - The entry key, cross-checked with the data from the main file
232 : : * when reloaded.
233 : : * - The length of the description.
234 : : * - The description data itself.
235 : : */
236 : : static bool
237 : 4 : test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key,
238 : : const PgStatShared_Common *header,
239 : : FILE *statfile)
240 : : {
241 : : char *description;
242 : : size_t len;
243 : 4 : const PgStatShared_CustomVarEntry *entry = (const PgStatShared_CustomVarEntry *) header;
244 : : bool found;
245 : 4 : uint32 magic_number = TEST_CUSTOM_VAR_MAGIC_NUMBER;
246 : :
247 : : /*
248 : : * First mark the main file with a magic number, keeping a trace that some
249 : : * auxiliary data will exist in the secondary statistics file.
250 : : */
251 [ - + ]: 4 : if (!write_chunk_s(statfile, &magic_number))
252 : 0 : return false;
253 : :
254 : : /* Open statistics file for writing. */
255 [ + + ]: 4 : if (!fd_description)
256 : : {
257 : 2 : fd_description = AllocateFile(TEST_CUSTOM_AUX_DATA_DESC, PG_BINARY_W);
258 [ - + ]: 2 : if (fd_description == NULL)
259 : : {
260 [ # # ]: 0 : ereport(LOG,
261 : : (errcode_for_file_access(),
262 : : errmsg("could not open statistics file \"%s\" for writing: %m",
263 : : TEST_CUSTOM_AUX_DATA_DESC)));
264 : 0 : return false;
265 : : }
266 : :
267 : : /* Initialize offset for secondary statistics file. */
268 : 2 : fd_description_offset = 0;
269 : : }
270 : :
271 : : /* Write offset to the main data file */
272 [ - + ]: 4 : if (!write_chunk_s(statfile, &fd_description_offset))
273 : 0 : return false;
274 : :
275 : : /*
276 : : * First write the entry key to the secondary statistics file. This will
277 : : * be cross-checked with the key read from main stats file at loading
278 : : * time.
279 : : */
280 [ - + ]: 4 : if (!write_chunk_s(fd_description, key))
281 : 0 : return false;
282 : 4 : fd_description_offset += sizeof(PgStat_HashKey);
283 : :
284 [ + + ]: 4 : if (!custom_stats_description_dsa)
285 : 2 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
286 : :
287 : : /* Handle entries without descriptions */
288 [ + - - + ]: 4 : if (!DsaPointerIsValid(entry->description) || !custom_stats_description_dsa)
289 : : {
290 : : /* length to description file */
291 : 0 : len = 0;
292 [ # # ]: 0 : if (!write_chunk_s(fd_description, &len))
293 : 0 : return false;
294 : 0 : fd_description_offset += sizeof(size_t);
295 : 0 : return true;
296 : : }
297 : :
298 : : /*
299 : : * Retrieve description from DSA, then write the length followed by the
300 : : * description.
301 : : */
302 : 4 : description = dsa_get_address(custom_stats_description_dsa,
303 : 4 : entry->description);
304 : 4 : len = strlen(description) + 1;
305 [ - + ]: 4 : if (!write_chunk_s(fd_description, &len))
306 : 0 : return false;
307 [ - + ]: 4 : if (!write_chunk(fd_description, description, len))
308 : 0 : return false;
309 : :
310 : : /*
311 : : * Update offset for next entry, counting for the length (size_t) of the
312 : : * description and the description contents.
313 : : */
314 : 4 : fd_description_offset += len + sizeof(size_t);
315 : 4 : return true;
316 : : }
317 : :
318 : : /*
319 : : * test_custom_stats_var_from_serialized_data() -
320 : : *
321 : : * Read auxiliary data (descriptions) for custom statistics entries from
322 : : * the secondary statistics file. This is called while loading the statistics
323 : : * at startup.
324 : : *
325 : : * See the top of test_custom_stats_var_to_serialized_data() for a
326 : : * detailed description of the data layout read here.
327 : : */
328 : : static bool
329 : 4 : test_custom_stats_var_from_serialized_data(const PgStat_HashKey *key,
330 : : PgStatShared_Common *header,
331 : : FILE *statfile)
332 : : {
333 : : PgStatShared_CustomVarEntry *entry;
334 : : dsa_pointer dp;
335 : : size_t len;
336 : : pgoff_t offset;
337 : : char *buffer;
338 : : bool found;
339 : 4 : uint32 magic_number = 0;
340 : : PgStat_HashKey file_key;
341 : :
342 : : /* Check the magic number first, in the main file. */
343 [ - + ]: 4 : if (!read_chunk_s(statfile, &magic_number))
344 : : {
345 [ # # ]: 0 : elog(WARNING, "failed to read magic number from statistics file");
346 : 0 : return false;
347 : : }
348 : :
349 [ - + ]: 4 : if (magic_number != TEST_CUSTOM_VAR_MAGIC_NUMBER)
350 : : {
351 [ # # ]: 0 : elog(WARNING, "found magic number %u from statistics file, should be %u",
352 : : magic_number, TEST_CUSTOM_VAR_MAGIC_NUMBER);
353 : 0 : return false;
354 : : }
355 : :
356 : : /*
357 : : * Read the offset from the main stats file, to be able to read the
358 : : * auxiliary data from the secondary statistics file.
359 : : */
360 [ - + ]: 4 : if (!read_chunk_s(statfile, &offset))
361 : : {
362 [ # # ]: 0 : elog(WARNING, "failed to read metadata offset from statistics file");
363 : 0 : return false;
364 : : }
365 : :
366 : : /* Open statistics file for reading if not already open */
367 [ + + ]: 4 : if (!fd_description)
368 : : {
369 : 2 : fd_description = AllocateFile(TEST_CUSTOM_AUX_DATA_DESC, PG_BINARY_R);
370 [ - + ]: 2 : if (fd_description == NULL)
371 : : {
372 [ # # ]: 0 : if (errno != ENOENT)
373 [ # # ]: 0 : ereport(LOG,
374 : : (errcode_for_file_access(),
375 : : errmsg("could not open statistics file \"%s\" for reading: %m",
376 : : TEST_CUSTOM_AUX_DATA_DESC)));
377 : 0 : pgstat_reset_of_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS);
378 : 0 : return false;
379 : : }
380 : : }
381 : :
382 : : /* Read data from the secondary statistics file, at the specified offset */
383 [ - + ]: 4 : if (fseeko(fd_description, offset, SEEK_SET) != 0)
384 : : {
385 [ # # ]: 0 : elog(WARNING, "could not seek in file \"%s\": %m",
386 : : TEST_CUSTOM_AUX_DATA_DESC);
387 : 0 : return false;
388 : : }
389 : :
390 : : /* Read the hash key from the secondary statistics file */
391 [ - + ]: 4 : if (!read_chunk_s(fd_description, &file_key))
392 : : {
393 [ # # ]: 0 : elog(WARNING, "failed to read hash key from file");
394 : 0 : return false;
395 : : }
396 : :
397 : : /* Check key consistency */
398 [ + - ]: 4 : if (file_key.kind != key->kind ||
399 [ + - ]: 4 : file_key.dboid != key->dboid ||
400 [ - + ]: 4 : file_key.objid != key->objid)
401 : : {
402 [ # # ]: 0 : elog(WARNING, "found entry key %u/%u/%" PRIu64 " not matching with %u/%u/%" PRIu64,
403 : : file_key.kind, file_key.dboid, file_key.objid,
404 : : key->kind, key->dboid, key->objid);
405 : 0 : return false;
406 : : }
407 : :
408 : 4 : entry = (PgStatShared_CustomVarEntry *) header;
409 : :
410 : : /* Read the description length and its data */
411 [ - + ]: 4 : if (!read_chunk_s(fd_description, &len))
412 : : {
413 [ # # ]: 0 : elog(WARNING, "failed to read metadata length from statistics file");
414 : 0 : return false;
415 : : }
416 : :
417 : : /* Handle empty descriptions */
418 [ - + ]: 4 : if (len == 0)
419 : : {
420 : 0 : entry->description = InvalidDsaPointer;
421 : 0 : return true;
422 : : }
423 : :
424 : : /* Initialize DSA if needed */
425 [ + + ]: 4 : if (!custom_stats_description_dsa)
426 : 2 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
427 : :
428 [ - + ]: 4 : if (!custom_stats_description_dsa)
429 : : {
430 [ # # ]: 0 : elog(WARNING, "could not access DSA for custom statistics descriptions");
431 : 0 : return false;
432 : : }
433 : :
434 : 4 : buffer = palloc(len);
435 [ - + ]: 4 : if (!read_chunk(fd_description, buffer, len))
436 : : {
437 : 0 : pfree(buffer);
438 [ # # ]: 0 : elog(WARNING, "failed to read description from file");
439 : 0 : return false;
440 : : }
441 : :
442 : : /* Allocate space in DSA and copy the description */
443 : 4 : dp = dsa_allocate(custom_stats_description_dsa, len);
444 : 4 : memcpy(dsa_get_address(custom_stats_description_dsa, dp), buffer, len);
445 : 4 : entry->description = dp;
446 : 4 : pfree(buffer);
447 : :
448 : 4 : return true;
449 : : }
450 : :
451 : : /*
452 : : * test_custom_stats_var_finish() -
453 : : *
454 : : * Cleanup function called at the end of statistics file operations.
455 : : * Handles closing files and cleanup based on the operation type.
456 : : */
457 : : static void
458 : 14 : test_custom_stats_var_finish(PgStat_StatsFileOp status)
459 : : {
460 [ + + + - ]: 14 : switch (status)
461 : : {
462 : 5 : case STATS_WRITE:
463 [ + + ]: 5 : if (!fd_description)
464 : 3 : return;
465 : :
466 : 2 : fd_description_offset = 0;
467 : :
468 : : /* Check for write errors and cleanup if necessary */
469 [ - + ]: 2 : if (ferror(fd_description))
470 : : {
471 [ # # ]: 0 : ereport(LOG,
472 : : (errcode_for_file_access(),
473 : : errmsg("could not write to file \"%s\": %m",
474 : : TEST_CUSTOM_AUX_DATA_DESC)));
475 : 0 : FreeFile(fd_description);
476 : 0 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
477 : : }
478 [ - + ]: 2 : else if (FreeFile(fd_description) < 0)
479 : : {
480 [ # # ]: 0 : ereport(LOG,
481 : : (errcode_for_file_access(),
482 : : errmsg("could not close file \"%s\": %m",
483 : : TEST_CUSTOM_AUX_DATA_DESC)));
484 : 0 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
485 : : }
486 : 2 : break;
487 : :
488 : 6 : case STATS_READ:
489 [ + + ]: 6 : if (fd_description)
490 : 2 : FreeFile(fd_description);
491 : :
492 : : /* Remove the file after reading */
493 [ - + ]: 6 : elog(DEBUG2, "removing file \"%s\"", TEST_CUSTOM_AUX_DATA_DESC);
494 : 6 : unlink(TEST_CUSTOM_AUX_DATA_DESC);
495 : 6 : break;
496 : :
497 : 3 : case STATS_DISCARD:
498 : : {
499 : : int ret;
500 : :
501 : : /* Attempt to remove the file */
502 : 3 : ret = unlink(TEST_CUSTOM_AUX_DATA_DESC);
503 [ + - ]: 3 : if (ret != 0)
504 : : {
505 [ + - ]: 3 : if (errno == ENOENT)
506 [ + - ]: 3 : elog(LOG,
507 : : "didn't need to unlink file \"%s\" - didn't exist",
508 : : TEST_CUSTOM_AUX_DATA_DESC);
509 : : else
510 [ # # ]: 0 : ereport(LOG,
511 : : (errcode_for_file_access(),
512 : : errmsg("could not unlink file \"%s\": %m",
513 : : TEST_CUSTOM_AUX_DATA_DESC)));
514 : : }
515 : : else
516 : : {
517 [ # # ]: 0 : ereport(LOG,
518 : : (errmsg_internal("unlinked file \"%s\"",
519 : : TEST_CUSTOM_AUX_DATA_DESC)));
520 : : }
521 : : }
522 : 3 : break;
523 : : }
524 : :
525 : 11 : fd_description = NULL;
526 : : }
527 : :
528 : : /*--------------------------------------------------------------------------
529 : : * Helper functions
530 : : *--------------------------------------------------------------------------
531 : : */
532 : :
533 : : /*
534 : : * test_custom_stats_var_fetch_entry
535 : : * Look up custom statistic by name
536 : : *
537 : : * Returns statistics entry from shared memory, or NULL if not found.
538 : : */
539 : : static PgStat_StatCustomVarEntry *
540 : 22 : test_custom_stats_var_fetch_entry(const char *stat_name)
541 : : {
542 : : /* Fetch entry by hashed name */
543 : 22 : return (PgStat_StatCustomVarEntry *)
544 : 22 : pgstat_fetch_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS,
545 : : InvalidOid,
546 : 22 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name),
547 : : NULL);
548 : : }
549 : :
550 : : /*--------------------------------------------------------------------------
551 : : * SQL-callable functions
552 : : *--------------------------------------------------------------------------
553 : : */
554 : :
555 : : /*
556 : : * test_custom_stats_var_create
557 : : * Create new custom statistic entry
558 : : *
559 : : * Initializes a statistics entry with the given name and description.
560 : : */
561 : 11 : PG_FUNCTION_INFO_V1(test_custom_stats_var_create);
562 : : Datum
563 : 10 : test_custom_stats_var_create(PG_FUNCTION_ARGS)
564 : : {
565 : : PgStat_EntryRef *entry_ref;
566 : : PgStatShared_CustomVarEntry *shared_entry;
567 : 10 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
568 : 10 : char *description = text_to_cstring(PG_GETARG_TEXT_PP(1));
569 : 10 : dsa_pointer dp = InvalidDsaPointer;
570 : : bool found;
571 : :
572 : : /* Validate name length first */
573 [ - + ]: 10 : if (strlen(stat_name) >= NAMEDATALEN)
574 [ # # ]: 0 : ereport(ERROR,
575 : : (errcode(ERRCODE_NAME_TOO_LONG),
576 : : errmsg("custom statistic name \"%s\" is too long", stat_name),
577 : : errdetail("Name must be less than %d characters.", NAMEDATALEN)));
578 : :
579 : : /* Initialize DSA and description provided */
580 [ + - ]: 10 : if (!custom_stats_description_dsa)
581 : 10 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
582 : :
583 [ - + ]: 10 : if (!custom_stats_description_dsa)
584 [ # # ]: 0 : ereport(ERROR,
585 : : (errmsg("could not access DSA for custom statistics descriptions")));
586 : :
587 : : /* Allocate space in DSA and copy description */
588 : 10 : dp = dsa_allocate(custom_stats_description_dsa, strlen(description) + 1);
589 : 20 : memcpy(dsa_get_address(custom_stats_description_dsa, dp),
590 : : description,
591 : 10 : strlen(description) + 1);
592 : :
593 : : /* Create or get existing entry */
594 : 10 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
595 : 10 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), true);
596 : :
597 [ - + ]: 10 : if (!entry_ref)
598 : 0 : PG_RETURN_VOID();
599 : :
600 : 10 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
601 : :
602 : : /* Zero-initialize statistics */
603 : 10 : memset(&shared_entry->stats, 0, sizeof(shared_entry->stats));
604 : :
605 : : /* Store description pointer */
606 : 10 : shared_entry->description = dp;
607 : :
608 : 10 : pgstat_unlock_entry(entry_ref);
609 : :
610 : 10 : PG_RETURN_VOID();
611 : : }
612 : :
613 : : /*
614 : : * test_custom_stats_var_update
615 : : * Increment custom statistic counter
616 : : *
617 : : * Increments call count in backend-local memory. Changes are flushed
618 : : * to shared memory by the statistics collector.
619 : : */
620 : 3 : PG_FUNCTION_INFO_V1(test_custom_stats_var_update);
621 : : Datum
622 : 20 : test_custom_stats_var_update(PG_FUNCTION_ARGS)
623 : : {
624 : 20 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
625 : : PgStat_EntryRef *entry_ref;
626 : : PgStat_StatCustomVarEntry *pending_entry;
627 : :
628 : : /* Get pending entry in local memory */
629 : 20 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
630 : 20 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL);
631 : :
632 : 20 : pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
633 : 20 : pending_entry->numcalls++;
634 : :
635 : 20 : PG_RETURN_VOID();
636 : : }
637 : :
638 : : /*
639 : : * test_custom_stats_var_match_entry
640 : : * Match custom variable stats entries by objid
641 : : *
642 : : * Callback for pgstat_drop_matching_entries(). Verifies that it filters
643 : : * entries by kind before invoking the callback.
644 : : */
645 : : static bool
646 : 6 : test_custom_stats_var_match_entry(PgStatShared_HashEntry *p, Datum match_data)
647 : : {
648 [ - + ]: 6 : if (p->key.kind != PGSTAT_KIND_TEST_CUSTOM_VAR_STATS)
649 [ # # ]: 0 : elog(ERROR,
650 : : "unexpected stats kind for custom stats match callback: %u",
651 : : p->key.kind);
652 : :
653 : 6 : return p->key.objid == DatumGetUInt64(match_data);
654 : : }
655 : :
656 : : /*
657 : : * test_custom_stats_var_drop
658 : : * Remove custom statistic entry
659 : : *
660 : : * Drops the named statistic from shared memory.
661 : : */
662 : 3 : PG_FUNCTION_INFO_V1(test_custom_stats_var_drop);
663 : : Datum
664 : 2 : test_custom_stats_var_drop(PG_FUNCTION_ARGS)
665 : : {
666 : 2 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
667 : :
668 : : /* Drop entry and request GC if the entry could not be freed */
669 [ - + ]: 2 : if (!pgstat_drop_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
670 : 2 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), false))
671 : 0 : pgstat_request_entry_refs_gc();
672 : :
673 : 2 : PG_RETURN_VOID();
674 : : }
675 : :
676 : : /*
677 : : * test_custom_stats_var_drop_matching
678 : : * Remove custom statistic entry by matching objid
679 : : *
680 : : * Drops the named statistic using pgstat_drop_matching_entries().
681 : : */
682 : 3 : PG_FUNCTION_INFO_V1(test_custom_stats_var_drop_matching);
683 : : Datum
684 : 2 : test_custom_stats_var_drop_matching(PG_FUNCTION_ARGS)
685 : : {
686 : 2 : char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
687 : 2 : uint64 stat_idx = PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name);
688 : :
689 : 2 : pgstat_drop_matching_entries(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS,
690 : : test_custom_stats_var_match_entry,
691 : : UInt64GetDatum(stat_idx));
692 : :
693 : 2 : PG_RETURN_VOID();
694 : : }
695 : :
696 : : /*
697 : : * test_custom_stats_var_match_kind
698 : : * Match custom variable stats entries by kind
699 : : *
700 : : * Callback for pgstat_drop_matching_entries().
701 : : */
702 : : static bool
703 : 81 : test_custom_stats_var_match_kind(PgStatShared_HashEntry *entry,
704 : : Datum match_data)
705 : : {
706 : 81 : return entry->key.kind == PGSTAT_KIND_TEST_CUSTOM_VAR_STATS;
707 : : }
708 : :
709 : : /*
710 : : * test_custom_stats_var_drop_all
711 : : * Remove all custom statistic entries
712 : : */
713 : 3 : PG_FUNCTION_INFO_V1(test_custom_stats_var_drop_all);
714 : : Datum
715 : 2 : test_custom_stats_var_drop_all(PG_FUNCTION_ARGS)
716 : : {
717 : 2 : pgstat_drop_matching_entries(PGSTAT_KIND_INVALID,
718 : : test_custom_stats_var_match_kind,
719 : : 0);
720 : :
721 : 2 : PG_RETURN_VOID();
722 : : }
723 : :
724 : : /*
725 : : * test_custom_stats_var_reset
726 : : * Reset all custom statistic entries
727 : : */
728 : 3 : PG_FUNCTION_INFO_V1(test_custom_stats_var_reset);
729 : : Datum
730 : 2 : test_custom_stats_var_reset(PG_FUNCTION_ARGS)
731 : : {
732 : 2 : pgstat_reset_of_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS);
733 : :
734 : 2 : PG_RETURN_VOID();
735 : : }
736 : :
737 : : /*
738 : : * test_custom_stats_var_report
739 : : * Retrieve custom statistic values
740 : : *
741 : : * Returns single row with statistic name, call count, and description if the
742 : : * statistic exists, otherwise returns no rows.
743 : : */
744 : 23 : PG_FUNCTION_INFO_V1(test_custom_stats_var_report);
745 : : Datum
746 : 36 : test_custom_stats_var_report(PG_FUNCTION_ARGS)
747 : : {
748 : : FuncCallContext *funcctx;
749 : : char *stat_name;
750 : : PgStat_StatCustomVarEntry *stat_entry;
751 : :
752 [ + + ]: 36 : if (SRF_IS_FIRSTCALL())
753 : : {
754 : : TupleDesc tupdesc;
755 : : MemoryContext oldcontext;
756 : :
757 : : /* Initialize SRF context */
758 : 22 : funcctx = SRF_FIRSTCALL_INIT();
759 : 22 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
760 : :
761 : : /* Get composite return type */
762 [ - + ]: 22 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
763 [ # # ]: 0 : elog(ERROR, "test_custom_stats_var_report: return type is not composite");
764 : :
765 : 22 : funcctx->tuple_desc = BlessTupleDesc(tupdesc);
766 : 22 : funcctx->max_calls = 1; /* single row result */
767 : :
768 : 22 : MemoryContextSwitchTo(oldcontext);
769 : : }
770 : :
771 : 36 : funcctx = SRF_PERCALL_SETUP();
772 : :
773 [ + + ]: 36 : if (funcctx->call_cntr < funcctx->max_calls)
774 : : {
775 : : Datum values[3];
776 : 22 : bool nulls[3] = {false, false, false};
777 : : HeapTuple tuple;
778 : : PgStat_EntryRef *entry_ref;
779 : : PgStatShared_CustomVarEntry *shared_entry;
780 : 22 : char *description = NULL;
781 : : bool found;
782 : :
783 : 22 : stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
784 : 22 : stat_entry = test_custom_stats_var_fetch_entry(stat_name);
785 : :
786 : : /* Return row only if entry exists */
787 [ + + ]: 22 : if (stat_entry)
788 : : {
789 : : /* Get entry ref to access shared entry */
790 : 14 : entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
791 : 14 : PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), false, NULL);
792 : :
793 [ + - ]: 14 : if (entry_ref)
794 : : {
795 : 14 : shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
796 : :
797 : : /* Get description from DSA if available */
798 [ + - ]: 14 : if (DsaPointerIsValid(shared_entry->description))
799 : : {
800 [ + - ]: 14 : if (!custom_stats_description_dsa)
801 : 14 : custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
802 : :
803 [ + - ]: 14 : if (custom_stats_description_dsa)
804 : 14 : description = dsa_get_address(custom_stats_description_dsa, shared_entry->description);
805 : : }
806 : : }
807 : :
808 : 14 : values[0] = PointerGetDatum(cstring_to_text(stat_name));
809 : 14 : values[1] = Int64GetDatum(stat_entry->numcalls);
810 : :
811 [ + - ]: 14 : if (description)
812 : 14 : values[2] = PointerGetDatum(cstring_to_text(description));
813 : : else
814 : 0 : nulls[2] = true;
815 : :
816 : 14 : tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
817 : 14 : SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
818 : : }
819 : : }
820 : :
821 : 22 : SRF_RETURN_DONE(funcctx);
822 : : }
|