Line data Source code
1 : /*--------------------------------------------------------------------------
2 : *
3 : * test_custom_fixed_stats.c
4 : * Test module for fixed-sized custom pgstats
5 : *
6 : * Copyright (c) 2025-2026, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * src/test/modules/test_custom_stats/test_custom_fixed_stats.c
10 : *
11 : * -------------------------------------------------------------------------
12 : */
13 :
14 : #include "postgres.h"
15 :
16 : #include "access/htup_details.h"
17 : #include "funcapi.h"
18 : #include "pgstat.h"
19 : #include "utils/builtins.h"
20 : #include "utils/pgstat_internal.h"
21 : #include "utils/timestamp.h"
22 :
23 3 : PG_MODULE_MAGIC_EXT(
24 : .name = "test_custom_fixed_stats",
25 : .version = PG_VERSION
26 : );
27 :
28 : /* Fixed-amount custom statistics entry */
29 : typedef struct PgStat_StatCustomFixedEntry
30 : {
31 : PgStat_Counter numcalls; /* # of times update function called */
32 : TimestampTz stat_reset_timestamp;
33 : } PgStat_StatCustomFixedEntry;
34 :
35 : typedef struct PgStatShared_CustomFixedEntry
36 : {
37 : LWLock lock; /* protects counters */
38 : uint32 changecount; /* for atomic reads */
39 : PgStat_StatCustomFixedEntry stats; /* current counters */
40 : PgStat_StatCustomFixedEntry reset_offset; /* reset baseline */
41 : } PgStatShared_CustomFixedEntry;
42 :
43 : /* Callbacks for fixed-amount statistics */
44 : static void test_custom_stats_fixed_init_shmem_cb(void *stats);
45 : static void test_custom_stats_fixed_reset_all_cb(TimestampTz ts);
46 : static void test_custom_stats_fixed_snapshot_cb(void);
47 :
48 : static const PgStat_KindInfo custom_stats = {
49 : .name = "test_custom_fixed_stats",
50 : .fixed_amount = true, /* exactly one entry */
51 : .write_to_file = true, /* persist to stats file */
52 :
53 : .shared_size = sizeof(PgStat_StatCustomFixedEntry),
54 : .shared_data_off = offsetof(PgStatShared_CustomFixedEntry, stats),
55 : .shared_data_len = sizeof(((PgStatShared_CustomFixedEntry *) 0)->stats),
56 :
57 : .init_shmem_cb = test_custom_stats_fixed_init_shmem_cb,
58 : .reset_all_cb = test_custom_stats_fixed_reset_all_cb,
59 : .snapshot_cb = test_custom_stats_fixed_snapshot_cb,
60 : };
61 :
62 : /*
63 : * Kind ID for test_custom_fixed_stats.
64 : */
65 : #define PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS 26
66 :
67 : /*--------------------------------------------------------------------------
68 : * Module initialization
69 : *--------------------------------------------------------------------------
70 : */
71 :
72 : void
73 3 : _PG_init(void)
74 : {
75 : /* Must be loaded via shared_preload_libraries */
76 3 : if (!process_shared_preload_libraries_in_progress)
77 0 : return;
78 :
79 : /* Register custom statistics kind */
80 3 : pgstat_register_kind(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS, &custom_stats);
81 : }
82 :
83 : /*
84 : * test_custom_stats_fixed_init_shmem_cb
85 : * Initialize shared memory structure
86 : */
87 : static void
88 3 : test_custom_stats_fixed_init_shmem_cb(void *stats)
89 : {
90 3 : PgStatShared_CustomFixedEntry *stats_shmem =
91 : (PgStatShared_CustomFixedEntry *) stats;
92 :
93 3 : LWLockInitialize(&stats_shmem->lock, LWTRANCHE_PGSTATS_DATA);
94 3 : }
95 :
96 : /*
97 : * test_custom_stats_fixed_reset_all_cb
98 : * Reset the fixed-sized stats
99 : */
100 : static void
101 1 : test_custom_stats_fixed_reset_all_cb(TimestampTz ts)
102 : {
103 : PgStatShared_CustomFixedEntry *stats_shmem =
104 1 : pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS);
105 :
106 : /* see explanation above PgStatShared_Archiver for the reset protocol */
107 1 : LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE);
108 1 : pgstat_copy_changecounted_stats(&stats_shmem->reset_offset,
109 1 : &stats_shmem->stats,
110 : sizeof(stats_shmem->stats),
111 : &stats_shmem->changecount);
112 1 : stats_shmem->stats.stat_reset_timestamp = ts;
113 1 : LWLockRelease(&stats_shmem->lock);
114 1 : }
115 :
116 : /*
117 : * test_custom_stats_fixed_snapshot_cb
118 : * Copy current stats to snapshot area
119 : */
120 : static void
121 3 : test_custom_stats_fixed_snapshot_cb(void)
122 : {
123 : PgStatShared_CustomFixedEntry *stats_shmem =
124 3 : pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS);
125 : PgStat_StatCustomFixedEntry *stat_snap =
126 3 : pgstat_get_custom_snapshot_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS);
127 3 : PgStat_StatCustomFixedEntry *reset_offset = &stats_shmem->reset_offset;
128 : PgStat_StatCustomFixedEntry reset;
129 :
130 3 : pgstat_copy_changecounted_stats(stat_snap,
131 3 : &stats_shmem->stats,
132 : sizeof(stats_shmem->stats),
133 : &stats_shmem->changecount);
134 :
135 3 : LWLockAcquire(&stats_shmem->lock, LW_SHARED);
136 3 : memcpy(&reset, reset_offset, sizeof(stats_shmem->stats));
137 3 : LWLockRelease(&stats_shmem->lock);
138 :
139 : /* Apply reset offsets */
140 : #define FIXED_COMP(fld) stat_snap->fld -= reset.fld;
141 3 : FIXED_COMP(numcalls);
142 : #undef FIXED_COMP
143 3 : }
144 :
145 : /*--------------------------------------------------------------------------
146 : * SQL-callable functions
147 : *--------------------------------------------------------------------------
148 : */
149 :
150 : /*
151 : * test_custom_stats_fixed_update
152 : * Increment call counter
153 : */
154 6 : PG_FUNCTION_INFO_V1(test_custom_stats_fixed_update);
155 : Datum
156 5 : test_custom_stats_fixed_update(PG_FUNCTION_ARGS)
157 : {
158 : PgStatShared_CustomFixedEntry *stats_shmem;
159 :
160 5 : stats_shmem = pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS);
161 :
162 5 : LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE);
163 :
164 5 : pgstat_begin_changecount_write(&stats_shmem->changecount);
165 5 : stats_shmem->stats.numcalls++;
166 5 : pgstat_end_changecount_write(&stats_shmem->changecount);
167 :
168 5 : LWLockRelease(&stats_shmem->lock);
169 :
170 5 : PG_RETURN_VOID();
171 : }
172 :
173 : /*
174 : * test_custom_stats_fixed_reset
175 : * Reset statistics by calling pgstat system
176 : */
177 1 : PG_FUNCTION_INFO_V1(test_custom_stats_fixed_reset);
178 : Datum
179 0 : test_custom_stats_fixed_reset(PG_FUNCTION_ARGS)
180 : {
181 0 : pgstat_reset_of_kind(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS);
182 :
183 0 : PG_RETURN_VOID();
184 : }
185 :
186 : /*
187 : * test_custom_stats_fixed_report
188 : * Return current counter values
189 : */
190 3 : PG_FUNCTION_INFO_V1(test_custom_stats_fixed_report);
191 : Datum
192 2 : test_custom_stats_fixed_report(PG_FUNCTION_ARGS)
193 : {
194 : TupleDesc tupdesc;
195 2 : Datum values[2] = {0};
196 2 : bool nulls[2] = {false};
197 : PgStat_StatCustomFixedEntry *stats;
198 :
199 : /* Take snapshot (applies reset offsets) */
200 2 : pgstat_snapshot_fixed(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS);
201 2 : stats = pgstat_get_custom_snapshot_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS);
202 :
203 : /* Build return tuple */
204 2 : tupdesc = CreateTemplateTupleDesc(2);
205 2 : TupleDescInitEntry(tupdesc, (AttrNumber) 1, "numcalls",
206 : INT8OID, -1, 0);
207 2 : TupleDescInitEntry(tupdesc, (AttrNumber) 2, "stats_reset",
208 : TIMESTAMPTZOID, -1, 0);
209 2 : BlessTupleDesc(tupdesc);
210 :
211 2 : values[0] = Int64GetDatum(stats->numcalls);
212 :
213 : /* Handle uninitialized timestamp (no reset yet) */
214 2 : if (stats->stat_reset_timestamp == 0)
215 : {
216 1 : nulls[1] = true;
217 : }
218 : else
219 : {
220 1 : values[1] = TimestampTzGetDatum(stats->stat_reset_timestamp);
221 : }
222 :
223 : /* Return as tuple */
224 2 : PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
225 : }
|