PostgreSQL Source Code git master
test_custom_var_stats.c
Go to the documentation of this file.
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 "utils/builtins.h"
19
21 .name = "test_custom_var_stats",
22 .version = PG_VERSION
23);
24
25/*--------------------------------------------------------------------------
26 * Macros and constants
27 *--------------------------------------------------------------------------
28 */
29
30/*
31 * Kind ID for test_custom_var_stats statistics.
32 */
33#define PGSTAT_KIND_TEST_CUSTOM_VAR_STATS 25
34
35/*
36 * Hash statistic name to generate entry index for pgstat lookup.
37 */
38#define PGSTAT_CUSTOM_VAR_STATS_IDX(name) hash_bytes_extended((const unsigned char *) name, strlen(name), 0)
39
40/*--------------------------------------------------------------------------
41 * Type definitions
42 *--------------------------------------------------------------------------
43 */
44
45/* Backend-local pending statistics before flush to shared memory */
47{
48 PgStat_Counter numcalls; /* times statistic was incremented */
50
51/* Shared memory statistics entry visible to all backends */
53{
54 PgStatShared_Common header; /* standard pgstat entry header */
55 PgStat_StatCustomVarEntry stats; /* custom statistics data */
57
58/*--------------------------------------------------------------------------
59 * Function prototypes
60 *--------------------------------------------------------------------------
61 */
62
63/* Flush callback: merge pending stats into shared memory */
65 bool nowait);
66
67/*--------------------------------------------------------------------------
68 * Custom kind configuration
69 *--------------------------------------------------------------------------
70 */
71
73 .name = "test_custom_var_stats",
74 .fixed_amount = false, /* variable number of entries */
75 .write_to_file = true, /* persist across restarts */
76 .track_entry_count = true, /* count active entries */
77 .accessed_across_databases = true, /* global statistics */
78 .shared_size = sizeof(PgStatShared_CustomVarEntry),
79 .shared_data_off = offsetof(PgStatShared_CustomVarEntry, stats),
80 .shared_data_len = sizeof(((PgStatShared_CustomVarEntry *) 0)->stats),
81 .pending_size = sizeof(PgStat_StatCustomVarEntry),
83};
84
85/*--------------------------------------------------------------------------
86 * Module initialization
87 *--------------------------------------------------------------------------
88 */
89
90void
92{
93 /* Must be loaded via shared_preload_libraries */
95 return;
96
97 /* Register custom statistics kind */
99}
100
101/*--------------------------------------------------------------------------
102 * Statistics callback functions
103 *--------------------------------------------------------------------------
104 */
105
106/*
107 * test_custom_stats_var_flush_pending_cb
108 * Merge pending backend statistics into shared memory
109 *
110 * Called by pgstat collector to flush accumulated local statistics
111 * to shared memory where other backends can read them.
112 *
113 * Returns false only if nowait=true and lock acquisition fails.
114 */
115static bool
117{
118 PgStat_StatCustomVarEntry *pending_entry;
119 PgStatShared_CustomVarEntry *shared_entry;
120
121 pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
122 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
123
124 if (!pgstat_lock_entry(entry_ref, nowait))
125 return false;
126
127 /* Add pending counts to shared totals */
128 shared_entry->stats.numcalls += pending_entry->numcalls;
129
130 pgstat_unlock_entry(entry_ref);
131
132 return true;
133}
134
135/*--------------------------------------------------------------------------
136 * Helper functions
137 *--------------------------------------------------------------------------
138 */
139
140/*
141 * test_custom_stats_var_fetch_entry
142 * Look up custom statistic by name
143 *
144 * Returns statistics entry from shared memory, or NULL if not found.
145 */
148{
149 /* Fetch entry by hashed name */
153 PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name));
154}
155
156/*--------------------------------------------------------------------------
157 * SQL-callable functions
158 *--------------------------------------------------------------------------
159 */
160
161/*
162 * test_custom_stats_var_create
163 * Create new custom statistic entry
164 *
165 * Initializes a zero-valued statistics entry in shared memory.
166 * Validates name length against NAMEDATALEN limit.
167 */
169Datum
171{
172 PgStat_EntryRef *entry_ref;
173 PgStatShared_CustomVarEntry *shared_entry;
174 char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
175
176 /* Validate name length first */
177 if (strlen(stat_name) >= NAMEDATALEN)
179 (errcode(ERRCODE_NAME_TOO_LONG),
180 errmsg("custom statistic name \"%s\" is too long", stat_name),
181 errdetail("Name must be less than %d characters.", NAMEDATALEN)));
182
183 /* Create or get existing entry */
185 PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), true);
186
187 if (!entry_ref)
189
190 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
191
192 /* Zero-initialize statistics */
193 memset(&shared_entry->stats, 0, sizeof(shared_entry->stats));
194
195 pgstat_unlock_entry(entry_ref);
196
198}
199
200/*
201 * test_custom_stats_var_update
202 * Increment custom statistic counter
203 *
204 * Increments call count in backend-local memory. Changes are flushed
205 * to shared memory by the statistics collector.
206 */
208Datum
210{
211 char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
212 PgStat_EntryRef *entry_ref;
213 PgStat_StatCustomVarEntry *pending_entry;
214
215 /* Get pending entry in local memory */
217 PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL);
218
219 pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
220 pending_entry->numcalls++;
221
223}
224
225/*
226 * test_custom_stats_var_drop
227 * Remove custom statistic entry
228 *
229 * Drops the named statistic from shared memory and requests
230 * garbage collection if needed.
231 */
233Datum
235{
236 char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
237
238 /* Drop entry and request GC if the entry could not be freed */
240 PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name)))
242
244}
245
246/*
247 * test_custom_stats_var_report
248 * Retrieve custom statistic values
249 *
250 * Returns single row with statistic name and call count if the
251 * statistic exists, otherwise returns no rows.
252 */
254Datum
256{
257 FuncCallContext *funcctx;
258 char *stat_name;
259 PgStat_StatCustomVarEntry *stat_entry;
260
261 if (SRF_IS_FIRSTCALL())
262 {
263 TupleDesc tupdesc;
264 MemoryContext oldcontext;
265
266 /* Initialize SRF context */
267 funcctx = SRF_FIRSTCALL_INIT();
268 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
269
270 /* Get composite return type */
271 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
272 elog(ERROR, "test_custom_stats_var_report: return type is not composite");
273
274 funcctx->tuple_desc = BlessTupleDesc(tupdesc);
275 funcctx->max_calls = 1; /* single row result */
276
277 MemoryContextSwitchTo(oldcontext);
278 }
279
280 funcctx = SRF_PERCALL_SETUP();
281
282 if (funcctx->call_cntr < funcctx->max_calls)
283 {
284 Datum values[2];
285 bool nulls[2] = {false, false};
286 HeapTuple tuple;
287
288 stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
289 stat_entry = test_custom_stats_var_fetch_entry(stat_name);
290
291 /* Return row only if entry exists */
292 if (stat_entry)
293 {
294 values[0] = PointerGetDatum(cstring_to_text(stat_name));
295 values[1] = Int64GetDatum(stat_entry->numcalls);
296
297 tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
298 SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
299 }
300 }
301
302 SRF_RETURN_DONE(funcctx);
303}
static Datum values[MAXATTR]
Definition: bootstrap.c:153
int errdetail(const char *fmt,...)
Definition: elog.c:1216
int errcode(int sqlerrcode)
Definition: elog.c:863
int errmsg(const char *fmt,...)
Definition: elog.c:1080
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:226
#define ereport(elevel,...)
Definition: elog.h:150
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
Definition: execTuples.c:2260
#define PG_RETURN_VOID()
Definition: fmgr.h:349
#define PG_GETARG_TEXT_PP(n)
Definition: fmgr.h:309
#define PG_FUNCTION_ARGS
Definition: fmgr.h:193
TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo, Oid *resultTypeId, TupleDesc *resultTupleDesc)
Definition: funcapi.c:276
#define SRF_IS_FIRSTCALL()
Definition: funcapi.h:304
#define SRF_PERCALL_SETUP()
Definition: funcapi.h:308
@ TYPEFUNC_COMPOSITE
Definition: funcapi.h:149
#define SRF_RETURN_NEXT(_funcctx, _result)
Definition: funcapi.h:310
#define SRF_FIRSTCALL_INIT()
Definition: funcapi.h:306
static Datum HeapTupleGetDatum(const HeapTupleData *tuple)
Definition: funcapi.h:230
#define SRF_RETURN_DONE(_funcctx)
Definition: funcapi.h:328
HeapTuple heap_form_tuple(TupleDesc tupleDescriptor, const Datum *values, const bool *isnull)
Definition: heaptuple.c:1117
bool process_shared_preload_libraries_in_progress
Definition: miscinit.c:1786
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:124
#define NAMEDATALEN
PgStat_EntryRef * pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
Definition: pgstat.c:1265
void * pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
Definition: pgstat.c:934
void pgstat_register_kind(PgStat_Kind kind, const PgStat_KindInfo *kind_info)
Definition: pgstat.c:1463
int64 PgStat_Counter
Definition: pgstat.h:67
void pgstat_request_entry_refs_gc(void)
Definition: pgstat_shmem.c:745
bool pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void pgstat_unlock_entry(PgStat_EntryRef *entry_ref)
Definition: pgstat_shmem.c:720
bool pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait)
Definition: pgstat_shmem.c:690
PgStat_EntryRef * pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, uint64 objid, bool nowait)
Definition: pgstat_shmem.c:729
static Datum Int64GetDatum(int64 X)
Definition: postgres.h:403
static Datum PointerGetDatum(const void *X)
Definition: postgres.h:332
uint64_t Datum
Definition: postgres.h:70
#define InvalidOid
Definition: postgres_ext.h:37
uint64 max_calls
Definition: funcapi.h:74
uint64 call_cntr
Definition: funcapi.h:65
MemoryContext multi_call_memory_ctx
Definition: funcapi.h:101
TupleDesc tuple_desc
Definition: funcapi.h:112
PgStat_StatCustomVarEntry stats
PgStatShared_Common * shared_stats
const char *const name
PG_FUNCTION_INFO_V1(test_custom_stats_var_create)
void _PG_init(void)
Datum test_custom_stats_var_update(PG_FUNCTION_ARGS)
struct PgStatShared_CustomVarEntry PgStatShared_CustomVarEntry
Datum test_custom_stats_var_drop(PG_FUNCTION_ARGS)
static const PgStat_KindInfo custom_stats
struct PgStat_StatCustomVarEntry PgStat_StatCustomVarEntry
PG_MODULE_MAGIC_EXT(.name="test_custom_var_stats",.version=PG_VERSION)
Datum test_custom_stats_var_create(PG_FUNCTION_ARGS)
Datum test_custom_stats_var_report(PG_FUNCTION_ARGS)
static bool test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait)
static PgStat_StatCustomVarEntry * test_custom_stats_var_fetch_entry(const char *stat_name)
#define PGSTAT_KIND_TEST_CUSTOM_VAR_STATS
#define PGSTAT_CUSTOM_VAR_STATS_IDX(name)
text * cstring_to_text(const char *s)
Definition: varlena.c:181
char * text_to_cstring(const text *t)
Definition: varlena.c:214
const char * name