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