From 12355fe2b0d1ac5f1d16ac3e1e5860bb4f641a2d Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Date: Fri, 8 Aug 2025 07:12:51 +0000
Subject: [PATCH v1 07/10] Adding vacuum_count to pg_stat_backend

Adding per backend number of manual vacuums triggered.

XXX: Bump catversion.
---
 doc/src/sgml/monitoring.sgml                 |  9 +++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/backend/utils/activity/pgstat_backend.c  |  4 ++++
 src/backend/utils/activity/pgstat_relation.c |  1 +
 src/backend/utils/adt/pgstatfuncs.c          |  3 ++-
 src/include/catalog/pg_proc.dat              |  6 +++---
 src/include/pgstat.h                         |  3 +++
 src/test/regress/expected/rules.out          |  3 ++-
 src/test/regress/expected/vacuum.out         | 15 +++++++++++++++
 src/test/regress/sql/vacuum.sql              | 11 +++++++++++
 10 files changed, 51 insertions(+), 5 deletions(-)
  14.5% doc/src/sgml/
   8.0% src/backend/utils/activity/
   7.2% src/backend/utils/adt/
  15.8% src/include/catalog/
   5.9% src/include/
  30.4% src/test/regress/expected/
  16.5% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index a6313a32299..1b3154d5326 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1267,6 +1267,15 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vacuum_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of manual vacuums triggered (not counting VACUUM FULL).
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
         <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d87fd38b8b2..6257bce86a1 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -919,6 +919,7 @@ CREATE VIEW pg_stat_backend AS
             S.idx_tup_fetch,
             S.idx_scan,
             S.idx_tup_read,
+            S.vacuum_count,
             S.stats_reset
     FROM pg_stat_get_backend_statistics(NULL) AS S;
 
diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c
index df072cb082f..58386662359 100644
--- a/src/backend/utils/activity/pgstat_backend.c
+++ b/src/backend/utils/activity/pgstat_backend.c
@@ -291,6 +291,7 @@ pgstat_flush_backend_entry_rel(PgStat_EntryRef *entry_ref)
 	BACKENDREL_ACC(idx_tup_fetch);
 	BACKENDREL_ACC(idx_scan);
 	BACKENDREL_ACC(idx_tup_read);
+	BACKENDREL_ACC(vacuum_count);
 #undef BACKENDREL_ACC
 
 	/*
@@ -473,6 +474,9 @@ PGSTAT_COUNT_BACKEND_FUNC(idx_tup_fetch)
 /* pgstat_count_backend_rel_idx_scan */
 PGSTAT_COUNT_BACKEND_FUNC(idx_scan)
 
+/* pgstat_count_backend_rel_vacuum_count */
+PGSTAT_COUNT_BACKEND_FUNC(vacuum_count)
+
 void
 pgstat_count_backend_rel_idx_tup_read(PgStat_Counter n)
 {
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 69df741cbf6..555f0815454 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -258,6 +258,7 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 		tabentry->last_vacuum_time = ts;
 		tabentry->vacuum_count++;
 		tabentry->total_vacuum_time += elapsedtime;
+		pgstat_count_backend_rel_vacuum_count();
 	}
 
 	pgstat_unlock_entry(entry_ref);
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 99486851281..bff5bfc0538 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -691,7 +691,7 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 Datum
 pg_stat_get_backend_statistics(PG_FUNCTION_ARGS)
 {
-#define PG_STAT_GET_BACKEND_STATS_COLS	7
+#define PG_STAT_GET_BACKEND_STATS_COLS	8
 	int			num_backends = pgstat_fetch_stat_numbackends();
 	int			curr_backend;
 	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
@@ -730,6 +730,7 @@ pg_stat_get_backend_statistics(PG_FUNCTION_ARGS)
 		values[i++] = Int64GetDatum(backend_stats->idx_tup_fetch);
 		values[i++] = Int64GetDatum(backend_stats->idx_scan);
 		values[i++] = Int64GetDatum(backend_stats->idx_tup_read);
+		values[i++] = Int64GetDatum(backend_stats->vacuum_count);
 
 		if (backend_stats->stat_reset_timestamp != 0)
 			values[i] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bb5608f492e..aae43548923 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5646,9 +5646,9 @@
   proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f',
   proretset => 't', provolatile => 's', proparallel => 'r',
   prorettype => 'record', proargtypes => 'int4',
-  proallargtypes => '{int4,int4,int8,int8,int8,int8,int8,timestamptz}',
-  proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{pid,pid,seq_scan,seq_tup_read,idx_tup_fetch,idx_scan,idx_tup_read,stats_reset}',
+  proallargtypes => '{int4,int4,int8,int8,int8,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  proargnames => '{pid,pid,seq_scan,seq_tup_read,idx_tup_fetch,idx_scan,idx_tup_read,vacuum_count,stats_reset}',
   prosrc => 'pg_stat_get_backend_statistics' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index f26b9e12567..558bfd3c123 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -495,6 +495,7 @@ typedef struct PgStat_Backend
 	PgStat_Counter idx_tup_fetch;
 	PgStat_Counter idx_scan;
 	PgStat_Counter idx_tup_read;
+	PgStat_Counter vacuum_count;
 } PgStat_Backend;
 
 typedef struct PgStat_BackendRelPending
@@ -504,6 +505,7 @@ typedef struct PgStat_BackendRelPending
 	PgStat_Counter idx_tup_fetch;
 	PgStat_Counter idx_scan;
 	PgStat_Counter idx_tup_read;
+	PgStat_Counter vacuum_count;
 } PgStat_BackendRelPending;
 
 /* ---------
@@ -588,6 +590,7 @@ extern void pgstat_count_backend_rel_seq_tup_read(void);
 extern void pgstat_count_backend_rel_idx_tup_fetch(void);
 extern void pgstat_count_backend_rel_idx_scan(void);
 extern void pgstat_count_backend_rel_idx_tup_read(PgStat_Counter n);
+extern void pgstat_count_backend_rel_vacuum_count(void);
 
 /*
  * Functions in pgstat_bgwriter.c
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2da8a8122f1..7df7fe37ed4 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1853,8 +1853,9 @@ pg_stat_backend| SELECT pid,
     idx_tup_fetch,
     idx_scan,
     idx_tup_read,
+    vacuum_count,
     stats_reset
-   FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, seq_scan, seq_tup_read, idx_tup_fetch, idx_scan, idx_tup_read, stats_reset);
+   FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, seq_scan, seq_tup_read, idx_tup_fetch, idx_scan, idx_tup_read, vacuum_count, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean,
     pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean,
     pg_stat_get_buf_alloc() AS buffers_alloc,
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 0abcc99989e..b7ab929084f 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -469,6 +469,7 @@ CREATE VIEW vac_option_tab_counts AS
   LEFT JOIN pg_class c ON s.relid = c.reltoastrelid
   WHERE c.relname = 'vac_option_tab' OR s.relname = 'vac_option_tab'
   ORDER BY rel;
+SELECT vacuum_count AS vacuum_count_before FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset
 VACUUM (PROCESS_TOAST TRUE) vac_option_tab;
 SELECT * FROM vac_option_tab_counts;
   rel  | vacuum_count 
@@ -497,6 +498,20 @@ SELECT * FROM vac_option_tab_counts;
  toast |            2
 (2 rows)
 
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT vacuum_count AS vacuum_count_after FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset
+SELECT :vacuum_count_after > :vacuum_count_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- Nothing is processed.
 VACUUM (PROCESS_MAIN FALSE, PROCESS_TOAST FALSE) vac_option_tab;
 SELECT * FROM vac_option_tab_counts;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index a72bdb5b619..b1bcd442c13 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -364,6 +364,9 @@ CREATE VIEW vac_option_tab_counts AS
   LEFT JOIN pg_class c ON s.relid = c.reltoastrelid
   WHERE c.relname = 'vac_option_tab' OR s.relname = 'vac_option_tab'
   ORDER BY rel;
+
+SELECT vacuum_count AS vacuum_count_before FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset
+
 VACUUM (PROCESS_TOAST TRUE) vac_option_tab;
 SELECT * FROM vac_option_tab_counts;
 VACUUM (PROCESS_TOAST FALSE) vac_option_tab;
@@ -374,6 +377,14 @@ VACUUM (PROCESS_TOAST FALSE, FULL) vac_option_tab; -- error
 -- Only the toast table is processed.
 VACUUM (PROCESS_MAIN FALSE) vac_option_tab;
 SELECT * FROM vac_option_tab_counts;
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+SELECT vacuum_count AS vacuum_count_after FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset
+
+SELECT :vacuum_count_after > :vacuum_count_before;
+
 -- Nothing is processed.
 VACUUM (PROCESS_MAIN FALSE, PROCESS_TOAST FALSE) vac_option_tab;
 SELECT * FROM vac_option_tab_counts;
-- 
2.34.1

