Skip to content

Commit 7553443

Browse files
committed
injection_points: Add some cumulative stats for injection points
This acts as a template of what can be achieved with the pluggable cumulative stats APIs introduced in 7949d95 for the variable-numbered case where stats entries are stored in the pgstats dshash, while being potentially useful on its own for injection points, say to add starting and/or stopping conditions based on the statistics (want to trigger a callback after N calls, for example?). Currently, the only data gathered is the number of times an injection point is run. More fields can always be added as required. All the routines related to the stats are located in their own file, called injection_stats.c in the test module injection_points, for clarity. The stats can be used only if the test module is loaded through shared_preload_libraries. The key of the dshash uses InvalidOid for the database, and an int4 hash of the injection point name as object ID. A TAP test is added to provide coverage for the new custom cumulative stats APIs, showing the persistency of the data across restarts, for example. Author: Michael Paquier Reviewed-by: Dmitry Dolgov, Bertrand Drouvot Discussion: https://postgr.es/m/Zmqm9j5EO0I4W8dx@paquier.xyz
1 parent 2eff9e6 commit 7553443

File tree

9 files changed

+332
-2
lines changed

9 files changed

+332
-2
lines changed

doc/src/sgml/xfunc.sgml

+5
Original file line numberDiff line numberDiff line change
@@ -3919,6 +3919,11 @@ extern PgStat_Kind pgstat_add_kind(PgStat_Kind kind,
39193919
type in <xref linkend="guc-shared-preload-libraries"/> so that it will
39203920
be loaded early during <productname>PostgreSQL</productname> startup.
39213921
</para>
3922+
3923+
<para>
3924+
An example describing how to register and use custom statistics can be
3925+
found in <filename>src/test/modules/injection_points</filename>.
3926+
</para>
39223927
</sect2>
39233928

39243929
<sect2 id="extend-cpp">

src/test/modules/injection_points/Makefile

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# src/test/modules/injection_points/Makefile
22

3-
MODULES = injection_points
4-
3+
MODULE_big = injection_points
4+
OBJS = \
5+
$(WIN32RES) \
6+
injection_points.o \
7+
injection_stats.o
58
EXTENSION = injection_points
69
DATA = injection_points--1.0.sql
710
PGFILEDESC = "injection_points - facility for injection points"
@@ -11,9 +14,13 @@ REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
1114

1215
ISOLATION = inplace
1316

17+
TAP_TESTS = 1
18+
1419
# The injection points are cluster-wide, so disable installcheck
1520
NO_INSTALLCHECK = 1
1621

22+
export enable_injection_points enable_injection_points
23+
1724
ifdef USE_PGXS
1825
PG_CONFIG = pg_config
1926
PGXS := $(shell $(PG_CONFIG) --pgxs)

src/test/modules/injection_points/injection_points--1.0.sql

+10
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,13 @@ CREATE FUNCTION injection_points_detach(IN point_name TEXT)
7474
RETURNS void
7575
AS 'MODULE_PATHNAME', 'injection_points_detach'
7676
LANGUAGE C STRICT PARALLEL UNSAFE;
77+
78+
--
79+
-- injection_points_stats_numcalls()
80+
--
81+
-- Reports statistics, if any, related to the given injection point.
82+
--
83+
CREATE FUNCTION injection_points_stats_numcalls(IN point_name TEXT)
84+
RETURNS bigint
85+
AS 'MODULE_PATHNAME', 'injection_points_stats_numcalls'
86+
LANGUAGE C STRICT;

src/test/modules/injection_points/injection_points.c

+27
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "postgres.h"
1919

2020
#include "fmgr.h"
21+
#include "injection_stats.h"
2122
#include "miscadmin.h"
2223
#include "nodes/pg_list.h"
2324
#include "nodes/value.h"
@@ -170,6 +171,9 @@ injection_points_cleanup(int code, Datum arg)
170171
char *name = strVal(lfirst(lc));
171172

172173
(void) InjectionPointDetach(name);
174+
175+
/* Remove stats entry */
176+
pgstat_drop_inj(name);
173177
}
174178
}
175179

@@ -182,6 +186,8 @@ injection_error(const char *name, const void *private_data)
182186
if (!injection_point_allowed(condition))
183187
return;
184188

189+
pgstat_report_inj(name);
190+
185191
elog(ERROR, "error triggered for injection point %s", name);
186192
}
187193

@@ -193,6 +199,8 @@ injection_notice(const char *name, const void *private_data)
193199
if (!injection_point_allowed(condition))
194200
return;
195201

202+
pgstat_report_inj(name);
203+
196204
elog(NOTICE, "notice triggered for injection point %s", name);
197205
}
198206

@@ -211,6 +219,8 @@ injection_wait(const char *name, const void *private_data)
211219
if (!injection_point_allowed(condition))
212220
return;
213221

222+
pgstat_report_inj(name);
223+
214224
/*
215225
* Use the injection point name for this custom wait event. Note that
216226
* this custom wait event name is not released, but we don't care much for
@@ -299,6 +309,10 @@ injection_points_attach(PG_FUNCTION_ARGS)
299309
inj_list_local = lappend(inj_list_local, makeString(pstrdup(name)));
300310
MemoryContextSwitchTo(oldctx);
301311
}
312+
313+
/* Add entry for stats */
314+
pgstat_create_inj(name);
315+
302316
PG_RETURN_VOID();
303317
}
304318

@@ -431,5 +445,18 @@ injection_points_detach(PG_FUNCTION_ARGS)
431445
MemoryContextSwitchTo(oldctx);
432446
}
433447

448+
/* Remove stats entry */
449+
pgstat_drop_inj(name);
450+
434451
PG_RETURN_VOID();
435452
}
453+
454+
455+
void
456+
_PG_init(void)
457+
{
458+
if (!process_shared_preload_libraries_in_progress)
459+
return;
460+
461+
pgstat_register_inj();
462+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*--------------------------------------------------------------------------
2+
*
3+
* injection_stats.c
4+
* Code for statistics of injection points.
5+
*
6+
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7+
* Portions Copyright (c) 1994, Regents of the University of California
8+
*
9+
* IDENTIFICATION
10+
* src/test/modules/injection_points/injection_stats.c
11+
*
12+
* -------------------------------------------------------------------------
13+
*/
14+
15+
#include "postgres.h"
16+
17+
#include "fmgr.h"
18+
19+
#include "common/hashfn.h"
20+
#include "injection_stats.h"
21+
#include "pgstat.h"
22+
#include "utils/builtins.h"
23+
#include "utils/pgstat_internal.h"
24+
25+
/* Structures for statistics of injection points */
26+
typedef struct PgStat_StatInjEntry
27+
{
28+
PgStat_Counter numcalls; /* number of times point has been run */
29+
} PgStat_StatInjEntry;
30+
31+
typedef struct PgStatShared_InjectionPoint
32+
{
33+
PgStatShared_Common header;
34+
PgStat_StatInjEntry stats;
35+
} PgStatShared_InjectionPoint;
36+
37+
static bool injection_stats_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
38+
39+
static const PgStat_KindInfo injection_stats = {
40+
.name = "injection_points",
41+
.fixed_amount = false, /* Bounded by the number of points */
42+
43+
/* Injection points are system-wide */
44+
.accessed_across_databases = true,
45+
46+
.shared_size = sizeof(PgStatShared_InjectionPoint),
47+
.shared_data_off = offsetof(PgStatShared_InjectionPoint, stats),
48+
.shared_data_len = sizeof(((PgStatShared_InjectionPoint *) 0)->stats),
49+
.pending_size = sizeof(PgStat_StatInjEntry),
50+
.flush_pending_cb = injection_stats_flush_cb,
51+
};
52+
53+
/*
54+
* Compute stats entry idx from point name with a 4-byte hash.
55+
*/
56+
#define PGSTAT_INJ_IDX(name) hash_bytes((const unsigned char *) name, strlen(name))
57+
58+
/*
59+
* Kind ID reserved for statistics of injection points.
60+
*/
61+
#define PGSTAT_KIND_INJECTION 129
62+
63+
/* Track if stats are loaded */
64+
static bool inj_stats_loaded = false;
65+
66+
/*
67+
* Callback for stats handling
68+
*/
69+
static bool
70+
injection_stats_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
71+
{
72+
PgStat_StatInjEntry *localent;
73+
PgStatShared_InjectionPoint *shfuncent;
74+
75+
localent = (PgStat_StatInjEntry *) entry_ref->pending;
76+
shfuncent = (PgStatShared_InjectionPoint *) entry_ref->shared_stats;
77+
78+
if (!pgstat_lock_entry(entry_ref, nowait))
79+
return false;
80+
81+
shfuncent->stats.numcalls += localent->numcalls;
82+
return true;
83+
}
84+
85+
/*
86+
* Support function for the SQL-callable pgstat* functions. Returns
87+
* a pointer to the injection point statistics struct.
88+
*/
89+
static PgStat_StatInjEntry *
90+
pgstat_fetch_stat_injentry(const char *name)
91+
{
92+
PgStat_StatInjEntry *entry = NULL;
93+
94+
if (!inj_stats_loaded)
95+
return NULL;
96+
97+
/* Compile the lookup key as a hash of the point name */
98+
entry = (PgStat_StatInjEntry *) pgstat_fetch_entry(PGSTAT_KIND_INJECTION,
99+
InvalidOid,
100+
PGSTAT_INJ_IDX(name));
101+
return entry;
102+
}
103+
104+
/*
105+
* Workhorse to do the registration work, called in _PG_init().
106+
*/
107+
void
108+
pgstat_register_inj(void)
109+
{
110+
pgstat_register_kind(PGSTAT_KIND_INJECTION, &injection_stats);
111+
112+
/* mark stats as loaded */
113+
inj_stats_loaded = true;
114+
}
115+
116+
/*
117+
* Report injection point creation.
118+
*/
119+
void
120+
pgstat_create_inj(const char *name)
121+
{
122+
PgStat_EntryRef *entry_ref;
123+
PgStatShared_InjectionPoint *shstatent;
124+
125+
/* leave if disabled */
126+
if (!inj_stats_loaded)
127+
return;
128+
129+
entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INJECTION, InvalidOid,
130+
PGSTAT_INJ_IDX(name), false);
131+
shstatent = (PgStatShared_InjectionPoint *) entry_ref->shared_stats;
132+
133+
/* initialize shared memory data */
134+
memset(&shstatent->stats, 0, sizeof(shstatent->stats));
135+
pgstat_unlock_entry(entry_ref);
136+
}
137+
138+
/*
139+
* Report injection point drop.
140+
*/
141+
void
142+
pgstat_drop_inj(const char *name)
143+
{
144+
/* leave if disabled */
145+
if (!inj_stats_loaded)
146+
return;
147+
148+
if (!pgstat_drop_entry(PGSTAT_KIND_INJECTION, InvalidOid,
149+
PGSTAT_INJ_IDX(name)))
150+
pgstat_request_entry_refs_gc();
151+
}
152+
153+
/*
154+
* Report statistics for injection point.
155+
*
156+
* This is simple because the set of stats to report currently is simple:
157+
* track the number of times a point has been run.
158+
*/
159+
void
160+
pgstat_report_inj(const char *name)
161+
{
162+
PgStat_EntryRef *entry_ref;
163+
PgStatShared_InjectionPoint *shstatent;
164+
PgStat_StatInjEntry *statent;
165+
166+
/* leave if disabled */
167+
if (!inj_stats_loaded)
168+
return;
169+
170+
entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INJECTION, InvalidOid,
171+
PGSTAT_INJ_IDX(name), false);
172+
173+
shstatent = (PgStatShared_InjectionPoint *) entry_ref->shared_stats;
174+
statent = &shstatent->stats;
175+
176+
/* Update the injection point statistics */
177+
statent->numcalls++;
178+
179+
pgstat_unlock_entry(entry_ref);
180+
}
181+
182+
/*
183+
* SQL function returning the number of times an injection point
184+
* has been called.
185+
*/
186+
PG_FUNCTION_INFO_V1(injection_points_stats_numcalls);
187+
Datum
188+
injection_points_stats_numcalls(PG_FUNCTION_ARGS)
189+
{
190+
char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
191+
PgStat_StatInjEntry *entry = pgstat_fetch_stat_injentry(name);
192+
193+
if (entry == NULL)
194+
PG_RETURN_NULL();
195+
196+
PG_RETURN_INT64(entry->numcalls);
197+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*--------------------------------------------------------------------------
2+
*
3+
* injection_stats.h
4+
* Definitions for statistics of injection points.
5+
*
6+
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7+
* Portions Copyright (c) 1994, Regents of the University of California
8+
*
9+
* IDENTIFICATION
10+
* src/test/modules/injection_points/injection_stats.h
11+
*
12+
* -------------------------------------------------------------------------
13+
*/
14+
15+
#ifndef INJECTION_STATS
16+
#define INJECTION_STATS
17+
18+
extern void pgstat_register_inj(void);
19+
extern void pgstat_create_inj(const char *name);
20+
extern void pgstat_drop_inj(const char *name);
21+
extern void pgstat_report_inj(const char *name);
22+
23+
#endif

src/test/modules/injection_points/meson.build

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ endif
66

77
injection_points_sources = files(
88
'injection_points.c',
9+
'injection_stats.c',
910
)
1011

1112
if host_system == 'windows'
@@ -42,4 +43,12 @@ tests += {
4243
'inplace',
4344
],
4445
},
46+
'tap': {
47+
'env': {
48+
'enable_injection_points': get_option('injection_points') ? 'yes' : 'no',
49+
},
50+
'tests': [
51+
't/001_stats.pl',
52+
],
53+
},
4554
}

0 commit comments

Comments
 (0)