From f3a4855aa5356c7d21f656f37d8799b91ba85b52 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 8 Jul 2024 13:44:14 +0900
Subject: [PATCH v6 5/5] 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.
---
 .../injection_points--1.0.sql                 |  11 ++
 .../injection_points/injection_points.c       |   3 +
 .../injection_points/injection_stats.c        | 155 +++++++++++++++++-
 .../injection_points/injection_stats.h        |   3 +
 .../modules/injection_points/t/001_stats.pl   |  11 +-
 5 files changed, 180 insertions(+), 3 deletions(-)

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 747a64e812..fa0b1d06ae 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -74,3 +74,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 eb411b9d44..f65bd83cc3 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();
@@ -418,6 +420,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);
 
diff --git a/src/test/modules/injection_points/injection_stats.c b/src/test/modules/injection_points/injection_stats.c
index c37b0b33d3..69560b7fdc 100644
--- a/src/test/modules/injection_points/injection_stats.c
+++ b/src/test/modules/injection_points/injection_stats.c
@@ -17,12 +17,13 @@
 #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 */
+/* Structures for statistics of injection points, variable-size */
 typedef struct PgStat_StatInjEntry
 {
 	PgStat_Counter numcalls;	/* number of times point has been run */
@@ -35,6 +36,9 @@ typedef struct PgStatShared_InjectionPoint
 } PgStatShared_InjectionPoint;
 
 static bool injection_stats_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+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 = {
 	.name = "injection_points",
@@ -50,18 +54,51 @@ static const PgStat_KindInfo injection_stats = {
 	.flush_pending_cb = injection_stats_flush_cb,
 };
 
+/* 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;
+
+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,
+};
+
 /*
  * Compute stats entry idx from point name with a 4-byte hash.
  */
 #define PGSTAT_INJ_IDX(name) hash_bytes((const unsigned char *) name, strlen(name))
 
 #define PGSTAT_KIND_INJECTION	PGSTAT_KIND_EXPERIMENTAL
+#define PGSTAT_KIND_INJECTION_FIXED	(PGSTAT_KIND_EXPERIMENTAL + 1)
+/* Position of fixed-numbered data in internal structures */
+#define PGSTAT_KIND_INJECTION_IDX	(PGSTAT_KIND_INJECTION_FIXED - PGSTAT_KIND_CUSTOM_MIN)
 
 /* Track if stats are loaded */
 static bool inj_stats_loaded = false;
 
 /*
- * Callback for stats handling
+ * Callbacks for stats handling
  */
 static bool
 injection_stats_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
@@ -79,6 +116,59 @@ injection_stats_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	return true;
 }
 
+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 =
+		(PgStatShared_InjectionPointFixed *)
+		pgStatLocal.shmem->custom_data[PGSTAT_KIND_INJECTION_IDX];
+
+	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 =
+		(PgStatShared_InjectionPointFixed *)
+		pgStatLocal.shmem->custom_data[PGSTAT_KIND_INJECTION_IDX];
+	PgStat_StatInjFixedEntry *stat_snap = (PgStat_StatInjFixedEntry *)
+		pgStatLocal.snapshot.custom_data[PGSTAT_KIND_INJECTION_IDX];
+	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
+}
+
 /*
  * Support function for the SQL-callable pgstat* functions.  Returns
  * a pointer to the injection point statistics struct.
@@ -105,6 +195,7 @@ void
 pgstat_register_inj(void)
 {
 	pgstat_register_kind(PGSTAT_KIND_INJECTION, &injection_stats);
+	pgstat_register_kind(PGSTAT_KIND_INJECTION_FIXED, &injection_stats_fixed);
 
 	/* mark stats as loaded */
 	inj_stats_loaded = true;
@@ -176,6 +267,30 @@ pgstat_report_inj(const char *name)
 	pgstat_unlock_entry(entry_ref);
 }
 
+/*
+ * 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_stats_loaded)
+		return;
+
+	stats_shmem = (PgStatShared_InjectionPointFixed *)
+		pgStatLocal.shmem->custom_data[PGSTAT_KIND_INJECTION_IDX];
+
+	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 the number of times an injection point
  * has been called.
@@ -192,3 +307,39 @@ injection_points_stats_numcalls(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT64(entry->numcalls);
 }
+
+/*
+ * 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;
+
+	pgstat_snapshot_fixed(PGSTAT_KIND_INJECTION_FIXED);
+	stats = pgStatLocal.snapshot.custom_data[PGSTAT_KIND_INJECTION_IDX];
+
+	/* 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/injection_stats.h b/src/test/modules/injection_points/injection_stats.h
index 3e99705483..cf68b25f7b 100644
--- a/src/test/modules/injection_points/injection_stats.h
+++ b/src/test/modules/injection_points/injection_stats.h
@@ -19,5 +19,8 @@ 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);
+extern void pgstat_report_inj_fixed(uint32 numattach,
+									uint32 numdetach,
+									uint32 numrun);
 
 #endif
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

