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