From cd82e799d2bd2d05d217b2eaec646e0298677302 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 18 Jul 2024 14:44:33 +0900
Subject: [PATCH v7 6/6] injection_points: Add example for fixed-numbered
 statistics

This acts as a template to show what can be achieved with fixed-numbered
stats (like WAL, bgwriter, etc.) for pluggable cumulative statistics.
---
 src/test/modules/injection_points/Makefile    |   3 +-
 .../injection_points--1.0.sql                 |  11 +
 .../injection_points/injection_points.c       |   4 +
 .../injection_points/injection_stats.h        |   7 +
 .../injection_points/injection_stats_fixed.c  | 192 ++++++++++++++++++
 src/test/modules/injection_points/meson.build |   1 +
 .../modules/injection_points/t/001_stats.pl   |  11 +-
 7 files changed, 227 insertions(+), 2 deletions(-)
 create mode 100644 src/test/modules/injection_points/injection_stats_fixed.c

diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile
index 7b9cd12a2a..ed28cd13a8 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -4,7 +4,8 @@ MODULE_big = injection_points
 OBJS = \
 	$(WIN32RES) \
 	injection_points.o \
-	injection_stats.o
+	injection_stats.o \
+	injection_stats_fixed.o
 EXTENSION = injection_points
 DATA = injection_points--1.0.sql
 PGFILEDESC = "injection_points - facility for injection points"
diff --git a/src/test/modules/injection_points/injection_points--1.0.sql b/src/test/modules/injection_points/injection_points--1.0.sql
index b98d571889..1b2a4938a9 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -84,3 +84,14 @@ CREATE FUNCTION injection_points_stats_numcalls(IN point_name TEXT)
 RETURNS bigint
 AS 'MODULE_PATHNAME', 'injection_points_stats_numcalls'
 LANGUAGE C STRICT;
+
+--
+-- injection_points_stats_fixed()
+--
+-- Reports fixed-numbered statistics for injection points.
+CREATE FUNCTION injection_points_stats_fixed(OUT numattach int8,
+   OUT numdetach int8,
+   OUT numrun int8)
+RETURNS record
+AS 'MODULE_PATHNAME', 'injection_points_stats_fixed'
+LANGUAGE C STRICT;
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index acec4e95b0..dc02be1bbf 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -297,6 +297,7 @@ injection_points_attach(PG_FUNCTION_ARGS)
 		condition.pid = MyProcPid;
 	}
 
+	pgstat_report_inj_fixed(1, 0, 0);
 	InjectionPointAttach(name, "injection_points", function, &condition,
 						 sizeof(InjectionPointCondition));
 
@@ -342,6 +343,7 @@ injection_points_run(PG_FUNCTION_ARGS)
 {
 	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
 
+	pgstat_report_inj_fixed(0, 0, 1);
 	INJECTION_POINT(name);
 
 	PG_RETURN_VOID();
@@ -432,6 +434,7 @@ injection_points_detach(PG_FUNCTION_ARGS)
 {
 	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
 
+	pgstat_report_inj_fixed(0, 1, 0);
 	if (!InjectionPointDetach(name))
 		elog(ERROR, "could not detach injection point \"%s\"", name);
 
@@ -459,4 +462,5 @@ _PG_init(void)
 		return;
 
 	pgstat_register_inj();
+	pgstat_register_inj_fixed();
 }
diff --git a/src/test/modules/injection_points/injection_stats.h b/src/test/modules/injection_points/injection_stats.h
index 3e99705483..d519f29f83 100644
--- a/src/test/modules/injection_points/injection_stats.h
+++ b/src/test/modules/injection_points/injection_stats.h
@@ -15,9 +15,16 @@
 #ifndef INJECTION_STATS
 #define INJECTION_STATS
 
+/* injection_stats.c */
 extern void pgstat_register_inj(void);
 extern void pgstat_create_inj(const char *name);
 extern void pgstat_drop_inj(const char *name);
 extern void pgstat_report_inj(const char *name);
 
+/* injection_stats_fixed.c */
+extern void pgstat_register_inj_fixed(void);
+extern void pgstat_report_inj_fixed(uint32 numattach,
+									uint32 numdetach,
+									uint32 numrun);
+
 #endif
diff --git a/src/test/modules/injection_points/injection_stats_fixed.c b/src/test/modules/injection_points/injection_stats_fixed.c
new file mode 100644
index 0000000000..75639328a8
--- /dev/null
+++ b/src/test/modules/injection_points/injection_stats_fixed.c
@@ -0,0 +1,192 @@
+/*--------------------------------------------------------------------------
+ *
+ * injection_stats_fixed.c
+ *		Code for fixed-numbered statistics of injection points.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		src/test/modules/injection_points/injection_stats_fixed.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+
+#include "common/hashfn.h"
+#include "funcapi.h"
+#include "injection_stats.h"
+#include "pgstat.h"
+#include "utils/builtins.h"
+#include "utils/pgstat_internal.h"
+
+/* Structures for statistics of injection points, fixed-size */
+typedef struct PgStat_StatInjFixedEntry
+{
+	PgStat_Counter numattach;	/* number of points attached */
+	PgStat_Counter numdetach;	/* number of points detached */
+	PgStat_Counter numrun;		/* number of points run */
+	TimestampTz stat_reset_timestamp;
+} PgStat_StatInjFixedEntry;
+
+typedef struct PgStatShared_InjectionPointFixed
+{
+	LWLock	lock;	/* protects all the counters */
+	uint32	changecount;
+	PgStat_StatInjFixedEntry stats;
+	PgStat_StatInjFixedEntry reset_offset;
+} PgStatShared_InjectionPointFixed;
+
+/* Callbacks for fixed-numbered stats */
+static void injection_stats_fixed_init_shmem_cb(void *stats);
+static void injection_stats_fixed_reset_all_cb(TimestampTz ts);
+static void injection_stats_fixed_snapshot_cb(void);
+
+static const PgStat_KindInfo injection_stats_fixed = {
+	.name = "injection_points_fixed",
+	.fixed_amount = true,
+
+	.shared_size = sizeof(PgStat_StatInjFixedEntry),
+	.shared_data_off = offsetof(PgStatShared_InjectionPointFixed, stats),
+	.shared_data_len = sizeof(((PgStatShared_InjectionPointFixed *) 0)->stats),
+
+	.init_shmem_cb = injection_stats_fixed_init_shmem_cb,
+	.reset_all_cb = injection_stats_fixed_reset_all_cb,
+	.snapshot_cb = injection_stats_fixed_snapshot_cb,
+};
+
+/*
+ * Kind ID reserved for statistics of injection points.
+ */
+#define PGSTAT_KIND_INJECTION_FIXED	130
+
+/* Track if fixed-numbered stats are loaded */
+static bool inj_fixed_loaded = false;
+
+static void
+injection_stats_fixed_init_shmem_cb(void *stats)
+{
+	PgStatShared_InjectionPointFixed *stats_shmem =
+		(PgStatShared_InjectionPointFixed *) stats;
+
+	LWLockInitialize(&stats_shmem->lock, LWTRANCHE_PGSTATS_DATA);
+}
+
+static void
+injection_stats_fixed_reset_all_cb(TimestampTz ts)
+{
+	PgStatShared_InjectionPointFixed *stats_shmem =
+		pgstat_get_custom_shmem_data(PGSTAT_KIND_INJECTION_FIXED);
+
+	LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE);
+	pgstat_copy_changecounted_stats(&stats_shmem->reset_offset,
+									&stats_shmem->stats,
+									sizeof(stats_shmem->stats),
+									&stats_shmem->changecount);
+	stats_shmem->stats.stat_reset_timestamp = ts;
+	LWLockRelease(&stats_shmem->lock);
+}
+
+static void
+injection_stats_fixed_snapshot_cb(void)
+{
+	PgStatShared_InjectionPointFixed *stats_shmem =
+		pgstat_get_custom_shmem_data(PGSTAT_KIND_INJECTION_FIXED);
+	PgStat_StatInjFixedEntry *stat_snap =
+		pgstat_get_custom_snapshot_data(PGSTAT_KIND_INJECTION_FIXED);
+	PgStat_StatInjFixedEntry *reset_offset = &stats_shmem->reset_offset;
+	PgStat_StatInjFixedEntry reset;
+
+	pgstat_copy_changecounted_stats(stat_snap,
+									&stats_shmem->stats,
+									sizeof(stats_shmem->stats),
+									&stats_shmem->changecount);
+
+	LWLockAcquire(&stats_shmem->lock, LW_SHARED);
+	memcpy(&reset, reset_offset, sizeof(stats_shmem->stats));
+	LWLockRelease(&stats_shmem->lock);
+
+	/* compensate by reset offsets */
+#define FIXED_COMP(fld) stat_snap->fld -= reset.fld;
+	FIXED_COMP(numattach);
+	FIXED_COMP(numdetach);
+	FIXED_COMP(numrun);
+#undef FIXED_COMP
+}
+
+/*
+ * Workhorse to do the registration work, called in _PG_init().
+ */
+void
+pgstat_register_inj_fixed(void)
+{
+	pgstat_register_kind(PGSTAT_KIND_INJECTION_FIXED, &injection_stats_fixed);
+
+	/* mark stats as loaded */
+	inj_fixed_loaded = true;
+}
+
+/*
+ * Report fixed number of statistics for an injection point.
+ */
+void
+pgstat_report_inj_fixed(uint32 numattach,
+						uint32 numdetach,
+						uint32 numrun)
+{
+	PgStatShared_InjectionPointFixed *stats_shmem;
+
+	/* leave if disabled */
+	if (!inj_fixed_loaded)
+		return;
+
+	stats_shmem = pgstat_get_custom_shmem_data(PGSTAT_KIND_INJECTION_FIXED);
+
+	pgstat_begin_changecount_write(&stats_shmem->changecount);
+	stats_shmem->stats.numattach += numattach;
+	stats_shmem->stats.numdetach += numdetach;
+	stats_shmem->stats.numrun += numrun;
+	pgstat_end_changecount_write(&stats_shmem->changecount);
+}
+
+/*
+ * SQL function returning fixed-numbered statistics for injection points.
+ */
+PG_FUNCTION_INFO_V1(injection_points_stats_fixed);
+Datum
+injection_points_stats_fixed(PG_FUNCTION_ARGS)
+{
+	TupleDesc	tupdesc;
+	Datum		values[3] = {0};
+	bool		nulls[3] = {0};
+	PgStat_StatInjFixedEntry *stats;
+
+	if (!inj_fixed_loaded)
+		PG_RETURN_NULL();
+
+	pgstat_snapshot_fixed(PGSTAT_KIND_INJECTION_FIXED);
+	stats = pgstat_get_custom_snapshot_data(PGSTAT_KIND_INJECTION_FIXED);
+
+	/* Initialise attributes information in the tuple descriptor */
+	tupdesc = CreateTemplateTupleDesc(3);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "numattach",
+					   INT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "numdetach",
+					   INT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "numrun",
+					   INT8OID, -1, 0);
+	BlessTupleDesc(tupdesc);
+
+	values[0] = Int64GetDatum(stats->numattach);
+	values[1] = Int64GetDatum(stats->numdetach);
+	values[2] = Int64GetDatum(stats->numrun);
+	nulls[0] = false;
+	nulls[1] = false;
+	nulls[2] = false;
+
+	/* Returns the record as Datum */
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build
index a52fe5121e..c9e357f644 100644
--- a/src/test/modules/injection_points/meson.build
+++ b/src/test/modules/injection_points/meson.build
@@ -7,6 +7,7 @@ endif
 injection_points_sources = files(
   'injection_points.c',
   'injection_stats.c',
+  'injection_stats_fixed.c',
 )
 
 if host_system == 'windows'
diff --git a/src/test/modules/injection_points/t/001_stats.pl b/src/test/modules/injection_points/t/001_stats.pl
index 7d5a96e522..e3c69b94ca 100644
--- a/src/test/modules/injection_points/t/001_stats.pl
+++ b/src/test/modules/injection_points/t/001_stats.pl
@@ -31,18 +31,27 @@ $node->safe_psql('postgres', "SELECT injection_points_run('stats-notice');");
 my $numcalls = $node->safe_psql('postgres',
 	"SELECT injection_points_stats_numcalls('stats-notice');");
 is($numcalls, '2', 'number of stats calls');
+my $fixedstats = $node->safe_psql('postgres',
+	"SELECT * FROM injection_points_stats_fixed();");
+is($fixedstats, '1|0|2', 'number of fixed stats');
 
 # Restart the node cleanly, stats should still be around.
 $node->restart;
 $numcalls = $node->safe_psql('postgres',
 	"SELECT injection_points_stats_numcalls('stats-notice');");
 is($numcalls, '2', 'number of stats after clean restart');
+$fixedstats = $node->safe_psql('postgres',
+	"SELECT * FROM injection_points_stats_fixed();");
+is($fixedstats, '1|0|2', 'number of fixed stats after clean restart');
 
 # On crash the stats are gone.
 $node->stop('immediate');
 $node->start;
 $numcalls = $node->safe_psql('postgres',
 	"SELECT injection_points_stats_numcalls('stats-notice');");
-is($numcalls, '', 'number of stats after clean restart');
+is($numcalls, '', 'number of stats after crash');
+$fixedstats = $node->safe_psql('postgres',
+	"SELECT * FROM injection_points_stats_fixed();");
+is($fixedstats, '0|0|0', 'number of fixed stats after crash');
 
 done_testing();
-- 
2.45.2

