From c7db46f953fdd6d5bf4a6198409910656528edbc Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Date: Thu, 7 Aug 2025 14:41:09 +0000
Subject: [PATCH v1 03/10] Adding seq_tup_read to pg_stat_backend

Adding per backend number of live rows fetched by sequential scans.

XXX: Bump catversion.
---
 doc/src/sgml/monitoring.sgml                |  9 +++++++++
 src/backend/access/heap/heapam.c            |  3 +++
 src/backend/access/heap/heapam_handler.c    |  1 +
 src/backend/catalog/system_views.sql        |  1 +
 src/backend/utils/activity/pgstat_backend.c |  4 ++++
 src/backend/utils/adt/pgstatfuncs.c         | 14 +++++++++-----
 src/include/catalog/pg_proc.dat             |  6 +++---
 src/include/pgstat.h                        |  3 +++
 src/test/regress/expected/rules.out         |  3 ++-
 src/test/regress/expected/stats.out         | 13 +++++++------
 src/test/regress/sql/stats.sql              |  7 ++++---
 11 files changed, 46 insertions(+), 18 deletions(-)
  11.1% doc/src/sgml/
   6.9% src/backend/access/heap/
   4.7% src/backend/utils/activity/
  24.2% src/backend/utils/adt/
  11.7% src/include/catalog/
   4.8% src/include/
  20.7% src/test/regress/expected/
  14.3% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 6868af55db4..3fdc3d639e6 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1231,6 +1231,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>seq_tup_read</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of live rows fetched by sequential scans.
+      </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/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index d9d6fb6c6ea..8561693258f 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1379,6 +1379,7 @@ heap_getnext(TableScanDesc sscan, ScanDirection direction)
 	 */
 
 	pgstat_count_heap_getnext(scan->rs_base.rs_rd);
+	pgstat_count_backend_rel_seq_tup_read();
 
 	return &scan->rs_ctup;
 }
@@ -1407,6 +1408,7 @@ heap_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *s
 	 */
 
 	pgstat_count_heap_getnext(scan->rs_base.rs_rd);
+	pgstat_count_backend_rel_seq_tup_read();
 
 	ExecStoreBufferHeapTuple(&scan->rs_ctup, slot,
 							 scan->rs_cbuf);
@@ -1555,6 +1557,7 @@ heap_getnextslot_tidrange(TableScanDesc sscan, ScanDirection direction,
 	 * the proper return buffer and return the tuple.
 	 */
 	pgstat_count_heap_getnext(scan->rs_base.rs_rd);
+	pgstat_count_backend_rel_seq_tup_read();
 
 	ExecStoreBufferHeapTuple(&scan->rs_ctup, slot, scan->rs_cbuf);
 	return true;
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index cb4bc35c93e..050ed0a79a3 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2335,6 +2335,7 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
 
 			/* Count successfully-fetched tuples as heap fetches */
 			pgstat_count_heap_getnext(scan->rs_rd);
+			pgstat_count_backend_rel_seq_tup_read();
 
 			return true;
 		}
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 3ab7802cc5c..ea2f4785602 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -915,6 +915,7 @@ CREATE VIEW pg_stat_backend AS
     SELECT
             S.pid,
             S.seq_scan,
+            S.seq_tup_read,
             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 8797dac68c7..85da2d23fc7 100644
--- a/src/backend/utils/activity/pgstat_backend.c
+++ b/src/backend/utils/activity/pgstat_backend.c
@@ -287,6 +287,7 @@ pgstat_flush_backend_entry_rel(PgStat_EntryRef *entry_ref)
 	(shbackendent->stats.stat += PendingBackendStats.pending_backendrel.stat)
 
 	BACKENDREL_ACC(heap_scan);
+	BACKENDREL_ACC(seq_tup_read);
 #undef BACKENDREL_ACC
 
 	/*
@@ -459,3 +460,6 @@ CppConcat(pgstat_count_backend_rel_,stat)(void)			\
 
 /* pgstat_count_backend_rel_heap_scan */
 PGSTAT_COUNT_BACKEND_FUNC(heap_scan)
+
+/* pgstat_count_backend_rel_seq_tup_read */
+PGSTAT_COUNT_BACKEND_FUNC(seq_tup_read)
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 2adfbdcb65c..c8f2b2a73d4 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	3
+#define PG_STAT_GET_BACKEND_STATS_COLS	4
 	int			num_backends = pgstat_fetch_stat_numbackends();
 	int			curr_backend;
 	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
@@ -708,6 +708,7 @@ pg_stat_get_backend_statistics(PG_FUNCTION_ARGS)
 		LocalPgBackendStatus *local_beentry;
 		PgBackendStatus *beentry;
 		PgStat_Backend *backend_stats;
+		int			i = 0;
 
 		/* Get the next one in the list */
 		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
@@ -719,17 +720,20 @@ pg_stat_get_backend_statistics(PG_FUNCTION_ARGS)
 
 		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
 
-		values[0] = Int32GetDatum(beentry->st_procpid);
+		values[i++] = Int32GetDatum(beentry->st_procpid);
 
 		if (!backend_stats)
 			continue;
 
-		values[1] = Int64GetDatum(backend_stats->heap_scan);
+		values[i++] = Int64GetDatum(backend_stats->heap_scan);
+		values[i++] = Int64GetDatum(backend_stats->seq_tup_read);
 
 		if (backend_stats->stat_reset_timestamp != 0)
-			values[2] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+			values[i] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
 		else
-			nulls[2] = true;
+			nulls[i] = true;
+
+		Assert(i + 1 == PG_STAT_GET_BACKEND_STATS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e87200374d8..797a3f22db5 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,timestamptz}',
-  proargmodes => '{i,o,o,o}',
-  proargnames => '{pid,pid,seq_scan,stats_reset}',
+  proallargtypes => '{int4,int4,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o}',
+  proargnames => '{pid,pid,seq_scan,seq_tup_read,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 7b4dacd27f8..d634fe8e746 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -491,11 +491,13 @@ typedef struct PgStat_Backend
 	PgStat_BktypeIO io_stats;
 	PgStat_WalCounters wal_counters;
 	PgStat_Counter heap_scan;
+	PgStat_Counter seq_tup_read;
 } PgStat_Backend;
 
 typedef struct PgStat_BackendRelPending
 {
 	PgStat_Counter heap_scan;
+	PgStat_Counter seq_tup_read;
 } PgStat_BackendRelPending;
 
 /* ---------
@@ -576,6 +578,7 @@ extern void pgstat_create_backend(ProcNumber procnum);
 
 /* used to track backend relations related stats */
 extern void pgstat_count_backend_rel_heap_scan(void);
+extern void pgstat_count_backend_rel_seq_tup_read(void);
 
 /*
  * Functions in pgstat_bgwriter.c
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 63f8f12b8a5..faf73d956e8 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1849,8 +1849,9 @@ pg_stat_archiver| SELECT archived_count,
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_backend| SELECT pid,
     seq_scan,
+    seq_tup_read,
     stats_reset
-   FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, seq_scan, stats_reset);
+   FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, seq_scan, seq_tup_read, 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/stats.out b/src/test/regress/expected/stats.out
index 937bf4bfc5b..c96d8d2ecae 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -118,7 +118,7 @@ SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch,
        pg_catalog.pg_statio_user_tables AS b
  WHERE t.relname='tenk2' AND b.relname='tenk2';
 COMMIT;
-SELECT seq_scan AS seq_scan_before
+SELECT seq_scan AS seq_scan_before, seq_tup_read AS seq_tup_read_before
   FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset
 -- test effects of TRUNCATE on n_live_tup/n_dead_tup counters
 CREATE TABLE trunc_stats_test(id serial);
@@ -221,12 +221,13 @@ SELECT st.seq_scan >= pr.seq_scan + 1,
  t        | t        | t        | t
 (1 row)
 
-SELECT seq_scan AS seq_scan_after
+SELECT seq_scan AS seq_scan_after, seq_tup_read AS seq_tup_read_after
   FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset
-SELECT :seq_scan_after > :seq_scan_before;
- ?column? 
-----------
- t
+SELECT :seq_scan_after > :seq_scan_before,
+       :seq_tup_read_after > :seq_tup_read_before;
+ ?column? | ?column? 
+----------+----------
+ t        | t
 (1 row)
 
 SELECT st.heap_blks_read + st.heap_blks_hit >= pr.heap_blks + cl.relpages,
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index b743352eadb..781b75d426a 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -38,7 +38,7 @@ SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch,
  WHERE t.relname='tenk2' AND b.relname='tenk2';
 COMMIT;
 
-SELECT seq_scan AS seq_scan_before
+SELECT seq_scan AS seq_scan_before, seq_tup_read AS seq_tup_read_before
   FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset
 
 -- test effects of TRUNCATE on n_live_tup/n_dead_tup counters
@@ -125,10 +125,11 @@ SELECT st.seq_scan >= pr.seq_scan + 1,
   FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
  WHERE st.relname='tenk2' AND cl.relname='tenk2';
 
-SELECT seq_scan AS seq_scan_after
+SELECT seq_scan AS seq_scan_after, seq_tup_read AS seq_tup_read_after
   FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset
 
-SELECT :seq_scan_after > :seq_scan_before;
+SELECT :seq_scan_after > :seq_scan_before,
+       :seq_tup_read_after > :seq_tup_read_before;
 
 SELECT st.heap_blks_read + st.heap_blks_hit >= pr.heap_blks + cl.relpages,
        st.idx_blks_read + st.idx_blks_hit >= pr.idx_blks + 1
-- 
2.34.1

