From 2ef5846ce48d453c6b4255564d1e7f715c4e1aa1 Mon Sep 17 00:00:00 2001
From: Dmitry Fomin <fomin.list@gmail.com>
Date: Sun, 7 Jun 2026 20:31:49 +0000
Subject: [PATCH v3 1/7] wait_event_timing: add --enable-wait-event-timing flag
 and wait_event_capture GUC

Introduce the compile-time option --enable-wait-event-timing
(meson: -Dwait_event_timing=true) defining USE_WAIT_EVENT_TIMING, and
the runtime GUC wait_event_capture (PGC_SUSET, enum off|stats, default
off).  This commit is scaffolding only: it wires the flag through both
build systems and registers the GUC with check/assign hooks, but adds
no instrumentation yet -- later commits in this series add the
recording hot path, the SQL surface, and the trace level.

In builds compiled without --enable-wait-event-timing, the GUC's check
hook rejects any value other than off (downgrading to off with a
warning for non-interactive sources), so the variable exists uniformly
for tooling but cannot be enabled.
---
 configure                                     |  32 +++++
 configure.ac                                  |   8 ++
 meson.build                                   |   1 +
 meson_options.txt                             |   3 +
 src/backend/utils/activity/Makefile           |   3 +-
 src/backend/utils/activity/meson.build        |   1 +
 .../utils/activity/wait_event_timing.c        | 109 ++++++++++++++++++
 src/backend/utils/misc/guc_parameters.dat     |   9 ++
 src/backend/utils/misc/guc_tables.c           |   1 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/pg_config.h.in                    |   3 +
 src/include/utils/guc.h                       |   1 +
 src/include/utils/guc_hooks.h                 |   2 +
 src/include/utils/wait_event_timing.h         |  53 +++++++++
 src/tools/pgindent/typedefs.list              |   1 +
 15 files changed, 228 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/utils/activity/wait_event_timing.c
 create mode 100644 src/include/utils/wait_event_timing.h

diff --git a/configure b/configure
index 35b0b72f0a7..a20dc7cd69e 100755
--- a/configure
+++ b/configure
@@ -850,6 +850,7 @@ enable_debug
 enable_profiling
 enable_coverage
 enable_dtrace
+enable_wait_event_timing
 enable_tap_tests
 enable_injection_points
 with_blocksize
@@ -1551,6 +1552,8 @@ Optional Features:
   --enable-profiling      build with profiling enabled
   --enable-coverage       build with coverage testing instrumentation
   --enable-dtrace         build with DTrace support
+  --enable-wait-event-timing
+                          build with wait event timing instrumentation
   --enable-tap-tests      enable TAP tests (requires Perl and IPC::Run)
   --enable-injection-points
                           enable injection points (for testing)
@@ -3633,6 +3636,35 @@ fi
 
 
 
+#
+# --enable-wait-event-timing adds wait event timing instrumentation
+#
+
+
+# Check whether --enable-wait-event-timing was given.
+if test "${enable_wait_event_timing+set}" = set; then :
+  enableval=$enable_wait_event_timing;
+  case $enableval in
+    yes)
+
+$as_echo "#define USE_WAIT_EVENT_TIMING 1" >>confdefs.h
+
+      ;;
+    no)
+      :
+      ;;
+    *)
+      as_fn_error $? "no argument expected for --enable-wait-event-timing option" "$LINENO" 5
+      ;;
+  esac
+
+else
+  enable_wait_event_timing=no
+
+fi
+
+
+
 #
 # TAP tests
 #
diff --git a/configure.ac b/configure.ac
index 0e624fe36b9..bafe6cc1fce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -225,6 +225,14 @@ fi
 AC_SUBST(DTRACEFLAGS)])
 AC_SUBST(enable_dtrace)
 
+#
+# --enable-wait-event-timing adds wait event timing instrumentation
+#
+PGAC_ARG_BOOL(enable, wait-event-timing, no,
+              [build with wait event timing instrumentation],
+              [AC_DEFINE([USE_WAIT_EVENT_TIMING], 1,
+                         [Define to 1 to build with wait event timing. (--enable-wait-event-timing)])])
+
 #
 # TAP tests
 #
diff --git a/meson.build b/meson.build
index d88a7a70308..93264e83230 100644
--- a/meson.build
+++ b/meson.build
@@ -505,6 +505,7 @@ meson_bin = find_program(meson_binpath, native: true)
 
 cdata.set('USE_ASSERT_CHECKING', get_option('cassert') ? 1 : false)
 cdata.set('USE_INJECTION_POINTS', get_option('injection_points') ? 1 : false)
+cdata.set('USE_WAIT_EVENT_TIMING', get_option('wait_event_timing') ? 1 : false)
 
 blocksize = get_option('blocksize').to_int() * 1024
 
diff --git a/meson_options.txt b/meson_options.txt
index 6a793f3e479..1f191d3a9d6 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -40,6 +40,9 @@ option('pgport', type: 'integer', value: 5432,
 option('cassert', type: 'boolean', value: false,
   description: 'Enable assertion checks (for debugging)')
 
+option('wait_event_timing', type: 'boolean', value: false,
+  description: 'Enable wait event timing instrumentation')
+
 option('tap_tests', type: 'feature', value: 'auto',
   description: 'Enable TAP tests')
 
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index 5fed953c28a..1c824e9b788 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -36,7 +36,8 @@ OBJS = \
 	pgstat_wal.o \
 	pgstat_xact.o \
 	wait_event.o \
-	wait_event_funcs.o
+	wait_event_funcs.o \
+	wait_event_timing.o
 
 # Force these dependencies to be known even without dependency info built:
 wait_event.o: wait_event.c $(top_builddir)/src/backend/utils/pgstat_wait_event.c
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 470b5dac402..13a85bb0d6a 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -20,6 +20,7 @@ backend_sources += files(
   'pgstat_subscription.c',
   'pgstat_wal.c',
   'pgstat_xact.c',
+  'wait_event_timing.c',
 )
 
 # this includes a .c file with contents generated in ../../../include/activity,
diff --git a/src/backend/utils/activity/wait_event_timing.c b/src/backend/utils/activity/wait_event_timing.c
new file mode 100644
index 00000000000..d11823adc57
--- /dev/null
+++ b/src/backend/utils/activity/wait_event_timing.c
@@ -0,0 +1,109 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait_event_timing.c
+ *	  Per-backend wait event timing instrumentation.
+ *
+ * Controlled by the wait_event_capture GUC (off | stats, default off)
+ * and the compile-time option --enable-wait-event-timing.
+ *
+ * This commit provides only the GUC scaffolding: the backing variable,
+ * the enum-value table consumed by guc.c, and the check/assign hooks.
+ * No instrumentation is performed yet -- later commits in the series add
+ * the recording hot path and the SQL surface.  The file compiles in both
+ * build configurations; in builds without --enable-wait-event-timing the
+ * check hook rejects any value other than off.
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/wait_event_timing.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "utils/guc.h"
+#include "utils/guc_hooks.h"
+#include "utils/wait_event_timing.h"
+
+/*
+ * GUC variable -- always defined so the GUC system has a backing variable
+ * even when compiled without --enable-wait-event-timing.  In stub builds
+ * the check hook below rejects any value other than OFF.
+ */
+int			wait_event_capture = WAIT_EVENT_CAPTURE_OFF;
+
+/*
+ * Enum value table consumed by guc.c.  Order matches the
+ * WaitEventCaptureLevel enum and the documented "off < stats" ordering.
+ */
+const struct config_enum_entry wait_event_capture_options[] = {
+	{"off", WAIT_EVENT_CAPTURE_OFF, false},
+	{"stats", WAIT_EVENT_CAPTURE_STATS, false},
+	{NULL, 0, false}
+};
+
+#ifdef USE_WAIT_EVENT_TIMING
+
+/*
+ * GUC check hook for wait_event_capture (timing build).
+ *
+ * All enum values are accepted at this level.  Side effects (attaching
+ * storage, etc.) are introduced by later commits; for now there is
+ * nothing to validate beyond the enum mapping that guc.c already did.
+ */
+bool
+check_wait_event_capture(int *newval, void **extra, GucSource source)
+{
+	return true;
+}
+
+/*
+ * GUC assign hook for wait_event_capture (timing build).
+ *
+ * No-op for now.  Later commits use this hook to drop in-flight wait
+ * state and manage per-session resources when the capture level changes.
+ */
+void
+assign_wait_event_capture(int newval, void *extra)
+{
+}
+
+#else							/* !USE_WAIT_EVENT_TIMING */
+
+/*
+ * GUC check hook for the stub build.  Any value other than 'off' is
+ * meaningless without --enable-wait-event-timing, so reject it -- or
+ * downgrade to 'off' with a warning when the value comes from a
+ * non-interactive source (config file at startup), so a leftover setting
+ * does not prevent the server from starting.
+ */
+bool
+check_wait_event_capture(int *newval, void **extra, GucSource source)
+{
+	if (*newval != WAIT_EVENT_CAPTURE_OFF)
+	{
+		if (source < PGC_S_INTERACTIVE)
+		{
+			ereport(WARNING,
+					(errmsg("wait_event_capture is not supported by this build, "
+							"forcing to \"off\""),
+					 errhint("Compile PostgreSQL with "
+							 "--enable-wait-event-timing.")));
+			*newval = WAIT_EVENT_CAPTURE_OFF;
+			return true;
+		}
+		GUC_check_errdetail("This build does not support wait event capture.");
+		GUC_check_errhint("Compile PostgreSQL with --enable-wait-event-timing.");
+		return false;
+	}
+	return true;
+}
+
+/* Stub assign hook -- nothing to do without compile-time support. */
+void
+assign_wait_event_capture(int newval, void *extra)
+{
+}
+
+#endif							/* USE_WAIT_EVENT_TIMING */
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index d421cdbde76..e6d9358e050 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -3440,6 +3440,15 @@
   boot_val => 'true',
 },
 
+{ name => 'wait_event_capture', type => 'enum', context => 'PGC_SUSET', group => 'STATS_CUMULATIVE',
+  short_desc => 'Controls collection of per-wait-event timing statistics.',
+  variable => 'wait_event_capture',
+  boot_val => 'WAIT_EVENT_CAPTURE_OFF',
+  options => 'wait_event_capture_options',
+  check_hook => 'check_wait_event_capture',
+  assign_hook => 'assign_wait_event_capture',
+},
+
 { name => 'wal_block_size', type => 'int', context => 'PGC_INTERNAL', group => 'PRESET_OPTIONS',
   short_desc => 'Shows the block size in the write ahead log.',
   flags => 'GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 90aa374b3ec..1a99e14bab2 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -103,6 +103,7 @@
 #include "utils/plancache.h"
 #include "utils/ps_status.h"
 #include "utils/rls.h"
+#include "utils/wait_event_timing.h"
 #include "utils/xml.h"
 
 #ifdef TRACE_SYNCSCAN
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 7958653077b..16043d74b44 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -704,6 +704,8 @@
 #track_cost_delay_timing = off
 #track_io_timing = off
 #track_wal_io_timing = off
+#wait_event_capture = off               # off, stats
+                                        # (requires --enable-wait-event-timing)
 #track_functions = none                 # none, pl, all
 #stats_fetch_consistency = cache        # cache, none, snapshot
 
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 4f8113c144b..ed0b7f26f9f 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -762,6 +762,9 @@
 /* Define to select unnamed POSIX semaphores. */
 #undef USE_UNNAMED_POSIX_SEMAPHORES
 
+/* Define to 1 to build with wait event timing. (--enable-wait-event-timing) */
+#undef USE_WAIT_EVENT_TIMING
+
 /* Define to select Win32-style semaphores. */
 #undef USE_WIN32_SEMAPHORES
 
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 8057d7870ad..285be02ef58 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -347,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
 extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
+extern PGDLLIMPORT const struct config_enum_entry wait_event_capture_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 307f4fbaefe..7896e9c7239 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -172,6 +172,8 @@ extern bool check_transaction_isolation(int *newval, void **extra, GucSource sou
 extern bool check_transaction_read_only(bool *newval, void **extra, GucSource source);
 extern void assign_transaction_timeout(int newval, void *extra);
 extern const char *show_unix_socket_permissions(void);
+extern bool check_wait_event_capture(int *newval, void **extra, GucSource source);
+extern void assign_wait_event_capture(int newval, void *extra);
 extern bool check_wal_buffers(int *newval, void **extra, GucSource source);
 extern bool check_wal_consistency_checking(char **newval, void **extra,
 										   GucSource source);
diff --git a/src/include/utils/wait_event_timing.h b/src/include/utils/wait_event_timing.h
new file mode 100644
index 00000000000..b44d19ecf35
--- /dev/null
+++ b/src/include/utils/wait_event_timing.h
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ *
+ * wait_event_timing.h
+ *	  Per-backend wait event timing instrumentation.
+ *
+ * This header declares the public surface of the wait-event-timing
+ * feature, gated by the compile-time option --enable-wait-event-timing
+ * (USE_WAIT_EVENT_TIMING) and the runtime GUC wait_event_capture.
+ *
+ * This commit introduces only the scaffolding: the capture-level enum
+ * and the GUC backing variable.  Later commits in the series add the
+ * recording hot path, the SQL-visible statistics, and the per-session
+ * trace ring.
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * src/include/utils/wait_event_timing.h
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAIT_EVENT_TIMING_H
+#define WAIT_EVENT_TIMING_H
+
+#include "c.h"
+
+/*
+ * Capture levels for the wait_event_capture GUC.  Order is significant:
+ * higher values are strict supersets of lower ones, so code paths can
+ * test for activation with "level >= WAIT_EVENT_CAPTURE_STATS".
+ *
+ *   OFF   - No instrumentation, no hot-path cost.
+ *   STATS - Aggregated per-event statistics (added by a later commit).
+ *
+ * A further TRACE level is added later in the series.
+ */
+typedef enum WaitEventCaptureLevel
+{
+	WAIT_EVENT_CAPTURE_OFF = 0,
+	WAIT_EVENT_CAPTURE_STATS,
+}			WaitEventCaptureLevel;
+
+/*
+ * Pin the enum ordering at compile time so future code that compares with
+ * >= against WAIT_EVENT_CAPTURE_STATS keeps working, and so reordering is
+ * caught at build time rather than via mysterious runtime mode switches.
+ */
+StaticAssertDecl(WAIT_EVENT_CAPTURE_OFF == 0 &&
+				 WAIT_EVENT_CAPTURE_STATS == 1,
+				 "WaitEventCaptureLevel values must be 0=OFF < 1=STATS");
+
+/* GUC variable (defined in wait_event_timing.c, even in stub builds). */
+extern PGDLLIMPORT int wait_event_capture;
+
+#endif							/* WAIT_EVENT_TIMING_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 56c1f997f88..30d405f8b52 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3421,6 +3421,7 @@ WaitEvent
 WaitEventActivity
 WaitEventBuffer
 WaitEventClient
+WaitEventCaptureLevel
 WaitEventCustomEntryByInfo
 WaitEventCustomEntryByName
 WaitEventIO
-- 
2.43.0

