From 295c963b9dc7e94fc4967600eea8238fcb017340 Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Date: Fri, 20 Jun 2025 12:11:21 +0000
Subject: [PATCH v1 3/4] Add the pg_stat_wait_event view

This new view reports wait events statistics (for non custom wait event).

This commit also adds documentation and regression tests.

XXX: Bump catalog version
---
 doc/src/sgml/monitoring.sgml         | 84 ++++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |  8 +++
 src/backend/utils/adt/pgstatfuncs.c  | 51 +++++++++++++++++
 src/include/catalog/pg_proc.dat      |  9 +++
 src/test/regress/expected/rules.out  |  5 ++
 src/test/regress/expected/stats.out  | 48 ++++++++++++++++
 src/test/regress/sql/stats.sql       | 22 ++++++++
 7 files changed, 227 insertions(+)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 4265a22d4de..4299dd3ffb2 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -518,6 +518,14 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_wait_event</structname><indexterm><primary>pg_stat_wait_event</primary></indexterm></entry>
+      <entry>One row per each non custom wait event, showing statistics about wait event activity. See
+       <link linkend="monitoring-pg-stat-wait-event-view">
+       <structname>pg_stat_wait_event</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_wal</structname><indexterm><primary>pg_stat_wal</primary></indexterm></entry>
       <entry>One row only, showing statistics about WAL activity. See
@@ -4822,6 +4830,76 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-wait-event-view">
+  <title><structname>pg_stat_wait_event</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_wait_event</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_wait_event</structname> view will contain
+   one row for each non custom wait event, showing statistics about that wait
+   event.
+  </para>
+
+  <table id="pg-stat-wait-event-view" xreflabel="pg_stat_wait_event">
+   <title><structname>pg_stat_wait_event</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>type</structfield> <type>text</type>
+      </para>
+      <para>
+       Wait event type
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>name</structfield> <type>text</type>
+      </para>
+      <para>
+       Wait event name
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>counts</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of times waiting on this wait event
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+      </para>
+      <para>
+       Time at which these statistics were last reset
+      </para></entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+
+ </sect2>
+
  <sect2 id="monitoring-stats-functions">
   <title>Statistics Functions</title>
 
@@ -5054,6 +5132,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_slru</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>wait_event</literal>: Reset all the counters shown in the
+          <structname>pg_stat_wait_event</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>wal</literal>: Reset all the counters shown in the
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 08f780a2e63..b3e93723964 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -942,6 +942,14 @@ CREATE VIEW pg_stat_slru AS
             s.stats_reset
     FROM pg_stat_get_slru() s;
 
+CREATE VIEW pg_stat_wait_event AS
+    SELECT
+            s.type,
+            s.name,
+            s.counts,
+            s.stats_reset
+    FROM pg_stat_get_wait_event() s;
+
 CREATE VIEW pg_stat_wal_receiver AS
     SELECT
             s.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1c12ddbae49..cfa6aefd95e 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1743,6 +1743,54 @@ pg_stat_get_slru(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of wait events.
+ */
+Datum
+pg_stat_get_wait_event(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_WAIT_EVENTS_COLS	4
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	int			i,
+				j;
+	PgStat_WaitEvent *stats;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* request wait event stats from the cumulative stats system */
+	stats = pgstat_fetch_stat_wait_event();
+
+	for (i = 0; i < NB_WAITCLASSTABLE_ENTRIES; i++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_WAIT_EVENTS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_WAIT_EVENTS_COLS] = {0};
+		WaitClassTableEntry *class = &WaitClassTable[i];
+		int			numWaitEvents;
+
+		numWaitEvents = class->numberOfEvents;
+
+		for (j = 0; j < numWaitEvents; j++)
+		{
+			const char *name;
+
+			name = get_wait_event_name_from_index(class->offSet + j);
+
+			if (!name)
+				continue;
+
+			values[0] = PointerGetDatum(cstring_to_text(class->className));
+			values[1] = PointerGetDatum(cstring_to_text(name));
+			values[2] = Int64GetDatum(stats->counts[class->offSet + j]);
+			values[3] = TimestampTzGetDatum(stats->stat_reset_timestamp);
+
+			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+		}
+	}
+
+	return (Datum) 0;
+}
+
 #define PG_STAT_GET_XACT_RELENTRY_INT64(stat)			\
 Datum													\
 CppConcat(pg_stat_get_xact_,stat)(PG_FUNCTION_ARGS)		\
@@ -1882,6 +1930,7 @@ pg_stat_reset_shared(PG_FUNCTION_ARGS)
 		pgstat_reset_of_kind(PGSTAT_KIND_IO);
 		XLogPrefetchResetStats();
 		pgstat_reset_of_kind(PGSTAT_KIND_SLRU);
+		pgstat_reset_of_kind(PGSTAT_KIND_WAIT_EVENT);
 		pgstat_reset_of_kind(PGSTAT_KIND_WAL);
 
 		PG_RETURN_VOID();
@@ -1901,6 +1950,8 @@ pg_stat_reset_shared(PG_FUNCTION_ARGS)
 		XLogPrefetchResetStats();
 	else if (strcmp(target, "slru") == 0)
 		pgstat_reset_of_kind(PGSTAT_KIND_SLRU);
+	else if (strcmp(target, "wait_event") == 0)
+		pgstat_reset_of_kind(PGSTAT_KIND_WAIT_EVENT);
 	else if (strcmp(target, "wal") == 0)
 		pgstat_reset_of_kind(PGSTAT_KIND_WAL);
 	else
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb4f7f50350..6ecea50a88c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6041,6 +6041,15 @@
   proargnames => '{name,blks_zeroed,blks_hit,blks_read,blks_written,blks_exists,flushes,truncates,stats_reset}',
   prosrc => 'pg_stat_get_slru' },
 
+{ oid => '8962', descr => 'statistics: information about wait events',
+  proname => 'pg_stat_get_wait_event', prorows => '300', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => '',
+  proallargtypes => '{text,text,int8,timestamptz}',
+  proargmodes => '{o,o,o,o}',
+  proargnames => '{type,name,counts,stats_reset}',
+  prosrc => 'pg_stat_get_wait_event' },
+
 { oid => '2978', descr => 'statistics: number of function calls',
   proname => 'pg_stat_get_function_calls', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6cf828ca8d0..e5f8938a176 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2277,6 +2277,11 @@ pg_stat_user_tables| SELECT relid,
     total_autoanalyze_time
    FROM pg_stat_all_tables
   WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wait_event| SELECT type,
+    name,
+    counts,
+    stats_reset
+   FROM pg_stat_get_wait_event() s(type, name, counts, stats_reset);
 pg_stat_wal| SELECT wal_records,
     wal_fpi,
     wal_bytes,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 776f1ad0e53..26d72dda3f1 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -236,6 +236,54 @@ FROM prevstats AS pr;
 (1 row)
 
 COMMIT;
+----
+-- Basic tests for wait events statistics
+---
+-- ensure there is no wait events missing in pg_stat_wait_event
+select count(1) > 0 from pg_stat_wait_event
+  where name not in (select name from pg_wait_events
+                     where type <> 'InjectionPoint' or type <> 'Extension')
+  and  type <> 'Extension';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- Test that reset_shared with wait_event specified as the stats type works
+SELECT count(1) AS counts_wait_event FROM pg_stat_wait_event WHERE counts > 0 \gset
+SELECT :counts_wait_event > 0;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT pg_stat_reset_shared('wait_event');
+ pg_stat_reset_shared 
+----------------------
+ 
+(1 row)
+
+SELECT count(1) < :counts_wait_event FROM pg_stat_wait_event WHERE counts > 0;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- Test wait event counters are incremented
+CREATE TABLE wait_event_stats_test(id serial);
+INSERT INTO wait_event_stats_test DEFAULT VALUES;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT counts > 0 FROM pg_stat_wait_event WHERE name = 'WalWrite' and type = 'IO';
+ ?column? 
+----------
+ t
+(1 row)
+
 ----
 -- Basic tests for track_functions
 ---
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 232ab8db8fa..0ea887a45fd 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -132,6 +132,28 @@ FROM prevstats AS pr;
 
 COMMIT;
 
+----
+-- Basic tests for wait events statistics
+---
+
+-- ensure there is no wait events missing in pg_stat_wait_event
+select count(1) > 0 from pg_stat_wait_event
+  where name not in (select name from pg_wait_events
+                     where type <> 'InjectionPoint' or type <> 'Extension')
+  and  type <> 'Extension';
+
+-- Test that reset_shared with wait_event specified as the stats type works
+SELECT count(1) AS counts_wait_event FROM pg_stat_wait_event WHERE counts > 0 \gset
+SELECT :counts_wait_event > 0;
+SELECT pg_stat_reset_shared('wait_event');
+SELECT count(1) < :counts_wait_event FROM pg_stat_wait_event WHERE counts > 0;
+
+-- Test wait event counters are incremented
+CREATE TABLE wait_event_stats_test(id serial);
+INSERT INTO wait_event_stats_test DEFAULT VALUES;
+SELECT pg_stat_force_next_flush();
+SELECT counts > 0 FROM pg_stat_wait_event WHERE name = 'WalWrite' and type = 'IO';
+
 ----
 -- Basic tests for track_functions
 ---
-- 
2.34.1

